Merge remote-tracking branch 'origin/master' into feat-settings-ui
# Conflicts: # go.mod # web/package.json # web/pnpm-lock.yamlfeat-otp-verification
commit
e6ef74fd8e
|
@ -68,7 +68,7 @@ services:
|
|||
volumes:
|
||||
- ${PWD}/data/nginx-proxy-manager/data:/data
|
||||
- ${PWD}/data/nginx-proxy-manager/letsencrypt:/etc/letsencrypt
|
||||
- ${PWD}/data/nginx/snippets:/config/nginx/snippets:ro
|
||||
- ${PWD}/data/nginx/snippets:/snippets:ro
|
||||
environment:
|
||||
TZ: 'Australia/Melbourne'
|
||||
authelia:
|
||||
|
|
|
@ -42,22 +42,67 @@ bootstrapping *Authelia*.
|
|||
|
||||
### SWAG Caveat
|
||||
|
||||
One current caveat of the [SWAG] implementation is that it serves Authelia as a subpath for each domain. We
|
||||
*__strongly recommend__* instead of using the out of the box method and guide for [SWAG] that you follow the
|
||||
[NGINX](nginx.md) guide (which *can be used* with [SWAG]) and run Authelia as it's own subdomain.
|
||||
One current caveat of the [SWAG] implementation is that it serves Authelia as a subpath for each domain by default. We
|
||||
*__strongly recommend__* instead of using the defaults that you configure Authelia as a subdomain if possible.
|
||||
|
||||
This is partly because Webauthn requires that the domain is an exact match when registering and authenticating and it is
|
||||
There are two potential ways to achieve this:
|
||||
|
||||
1. Adjust the default `authelia-server.conf` as per the included directions.
|
||||
2. Use the supplementary configuration snippets provided officially by Authelia.
|
||||
|
||||
This is partly because WebAuthn requires that the domain is an exact match when registering and authenticating and it is
|
||||
possible that due to web standards this will never change.
|
||||
|
||||
In addition this represents a bad user experience in some instances such as:
|
||||
|
||||
- Users sometimes visit the `https://app.example.com/authelia` URL which doesn't automatically redirect the user to
|
||||
`https://app.example.com` (if they visit `https://app.example.com` then they'll be redirected to authenticate then
|
||||
redirected back to their original URL).
|
||||
- Administrators may wish to setup OpenID Connect 1.0 in which case it also doesn't represent a good user experience.
|
||||
- Users sometimes visit the `https://app.example.com/authelia` URL which doesn't automatically redirect the user to
|
||||
`https://app.example.com` (if they visit `https://app.example.com` then they'll be redirected to authenticate then
|
||||
redirected back to their original URL).
|
||||
- Administrators may wish to setup [OpenID Connect 1.0](../../configuration/identity-providers/open-id-connect.md) in
|
||||
which case it also doesn't represent a good user experience as the `issuer` will be
|
||||
`https://app.example.com/authelia` for example
|
||||
- Using the [SWAG] default configurations are more difficult to support as our specific familiarity is with our own
|
||||
example snippets
|
||||
|
||||
Taking these factors into consideration we're adapting our [SWAG] guide to use what we consider best for the users and
|
||||
most easily supported. Users who wish to use the [SWAG] guide are free to do so but may not receive the same support.
|
||||
#### Option 1: Adjusting the Default Configuration
|
||||
|
||||
Open the generated `authelia-server.conf`. Adjust the following sections. There are two snippets, one before and one
|
||||
after. The only lines that change are the `set $authelia_backend` lines, and this configuration assumes you're
|
||||
serving Authelia at `auth.example.com`.
|
||||
|
||||
```nginx
|
||||
## Set $authelia_backend to route requests to the current domain by default
|
||||
set $authelia_backend $http_host;
|
||||
## In order for Webauthn to work with multiple domains authelia must operate on a separate subdomain
|
||||
## To use authelia on a separate subdomain:
|
||||
## * comment the $authelia_backend line above
|
||||
## * rename /config/nginx/proxy-confs/authelia.conf.sample to /config/nginx/proxy-confs/authelia.conf
|
||||
## * make sure that your dns has a cname set for authelia
|
||||
## * uncomment the $authelia_backend line below and change example.com to your domain
|
||||
## * restart the swag container
|
||||
#set $authelia_backend authelia.example.com;
|
||||
|
||||
return 302 https://$authelia_backend/authelia/?rd=$target_url;
|
||||
```
|
||||
|
||||
```nginx
|
||||
## Set $authelia_backend to route requests to the current domain by default
|
||||
# set $authelia_backend $http_host;
|
||||
## In order for Webauthn to work with multiple domains authelia must operate on a separate subdomain
|
||||
## To use authelia on a separate subdomain:
|
||||
## * comment the $authelia_backend line above
|
||||
## * rename /config/nginx/proxy-confs/authelia.conf.sample to /config/nginx/proxy-confs/authelia.conf
|
||||
## * make sure that your dns has a cname set for authelia
|
||||
## * uncomment the $authelia_backend line below and change example.com to your domain
|
||||
## * restart the swag container
|
||||
set $authelia_backend auth.example.com;
|
||||
|
||||
return 302 https://$authelia_backend/authelia/?rd=$target_url;
|
||||
```
|
||||
|
||||
#### Option 2: Using the Authelia Supplementary Configuration Snippets
|
||||
|
||||
See standard [NGINX](nginx.md) guide (which *can be used* with [SWAG]) and run Authelia as it's own subdomain.
|
||||
|
||||
## Trusted Proxies
|
||||
|
||||
|
@ -102,6 +147,8 @@ services:
|
|||
- '443:443'
|
||||
volumes:
|
||||
- ${PWD}/data/swag:/config
|
||||
#- ${PWD}/data/nginx/snippets:/snippets:ro
|
||||
## Uncomment the above line if you want to use the Authelia configuration snippets.
|
||||
environment:
|
||||
PUID: '1000'
|
||||
PGID: '1000'
|
||||
|
|
4
go.mod
4
go.mod
|
@ -13,7 +13,7 @@ require (
|
|||
github.com/go-asn1-ber/asn1-ber v1.5.4
|
||||
github.com/go-crypt/crypt v0.2.6
|
||||
github.com/go-ldap/ldap/v3 v3.4.4
|
||||
github.com/go-rod/rod v0.112.5
|
||||
github.com/go-rod/rod v0.112.6
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/go-webauthn/webauthn v0.8.2
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
|
@ -33,7 +33,7 @@ require (
|
|||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/ory/fosite v0.44.0
|
||||
github.com/ory/herodot v0.9.13
|
||||
github.com/ory/x v0.0.537
|
||||
github.com/ory/x v0.0.541
|
||||
github.com/otiai10/copy v1.9.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pquerna/otp v1.4.0
|
||||
|
|
8
go.sum
8
go.sum
|
@ -160,8 +160,8 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
|
|||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-rod/rod v0.112.5 h1:2mH97UK8We4D2MfX388WqPjG1lDbxx8lLi5MzfvnEo0=
|
||||
github.com/go-rod/rod v0.112.5/go.mod h1:ElViL9ABbcshNQw93+11FrYRH92RRhMKleuILo6+5V0=
|
||||
github.com/go-rod/rod v0.112.6 h1:zMirUmhsBeshMWyf285BD0UGtGq54HfThLDGSjcP3lU=
|
||||
github.com/go-rod/rod v0.112.6/go.mod h1:ElViL9ABbcshNQw93+11FrYRH92RRhMKleuILo6+5V0=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
|
@ -387,8 +387,8 @@ github.com/ory/go-convenience v0.1.0/go.mod h1:uEY/a60PL5c12nYz4V5cHY03IBmwIAEm8
|
|||
github.com/ory/herodot v0.9.13 h1:cN/Z4eOkErl/9W7hDIDLb79IO/bfsH+8yscBjRpB4IU=
|
||||
github.com/ory/herodot v0.9.13/go.mod h1:IWDs9kSvFQqw/cQ8zi5ksyYvITiUU4dI7glUrhZcJYo=
|
||||
github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM=
|
||||
github.com/ory/x v0.0.537 h1:FB8Tioza6pihvy/RsVNzX08Qg3/VpIhI9vBnEQ4iFmQ=
|
||||
github.com/ory/x v0.0.537/go.mod h1:CQopDsCC9t0tQsddE9UlyRFVEFd2xjKBVcw4nLMMMS0=
|
||||
github.com/ory/x v0.0.541 h1:rp8AD7X5/WiZIJws5kBwFgXoSeunxNMD54QV58+pcew=
|
||||
github.com/ory/x v0.0.541/go.mod h1:ktXUvx51Ok1gMGr3ysvktanqr+eiB4FXglt4nF4w2Uo=
|
||||
github.com/otiai10/copy v1.9.0 h1:7KFNiCgZ91Ru4qW4CWPf/7jqtxLagGRmIxWldPP9VY4=
|
||||
github.com/otiai10/copy v1.9.0/go.mod h1:hsfX19wcn0UWIHUQ3/4fHuehhk2UyArQ9dVFAn3FczI=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
|
|
|
@ -777,3 +777,12 @@ Layouts:
|
|||
const (
|
||||
fmtLogServerListening = "Listening for %s connections on '%s' path '%s'"
|
||||
)
|
||||
|
||||
const (
|
||||
logFieldService = "service"
|
||||
logFieldFile = "file"
|
||||
logFieldOP = "op"
|
||||
|
||||
serviceTypeServer = "server"
|
||||
serviceTypeWatcher = "watcher"
|
||||
)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -23,11 +24,12 @@ import (
|
|||
// NewServerService creates a new ServerService with the appropriate logger etc.
|
||||
func NewServerService(name string, server *fasthttp.Server, listener net.Listener, paths []string, isTLS bool, log *logrus.Logger) (service *ServerService) {
|
||||
return &ServerService{
|
||||
name: name,
|
||||
server: server,
|
||||
listener: listener,
|
||||
paths: paths,
|
||||
isTLS: isTLS,
|
||||
log: log.WithFields(map[string]any{"service": "server", "server": name}),
|
||||
log: log.WithFields(map[string]any{logFieldService: serviceTypeServer, serviceTypeServer: name}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,10 +55,11 @@ func NewFileWatcherService(name, path string, reload ProviderReload, log *logrus
|
|||
return nil, err
|
||||
}
|
||||
|
||||
entry := log.WithFields(map[string]any{"service": "watcher", "watcher": name})
|
||||
entry := log.WithFields(map[string]any{logFieldService: serviceTypeWatcher, serviceTypeWatcher: name})
|
||||
|
||||
if info.IsDir() {
|
||||
service = &FileWatcherService{
|
||||
name: name,
|
||||
watcher: watcher,
|
||||
reload: reload,
|
||||
log: entry,
|
||||
|
@ -64,6 +67,7 @@ func NewFileWatcherService(name, path string, reload ProviderReload, log *logrus
|
|||
}
|
||||
} else {
|
||||
service = &FileWatcherService{
|
||||
name: name,
|
||||
watcher: watcher,
|
||||
reload: reload,
|
||||
log: entry,
|
||||
|
@ -86,12 +90,25 @@ type ProviderReload interface {
|
|||
|
||||
// Service represents the required methods to support handling a service.
|
||||
type Service interface {
|
||||
// ServiceType returns the type name for the Service.
|
||||
ServiceType() string
|
||||
|
||||
// ServiceName returns the individual name for the Service.
|
||||
ServiceName() string
|
||||
|
||||
// Run performs the running operations for the Service.
|
||||
Run() (err error)
|
||||
|
||||
// Shutdown perform the shutdown cleanup and termination operations for the Service.
|
||||
Shutdown()
|
||||
|
||||
// Log returns the logger configured for the service.
|
||||
Log() *logrus.Entry
|
||||
}
|
||||
|
||||
// ServerService is a Service which runs a webserver.
|
||||
type ServerService struct {
|
||||
name string
|
||||
server *fasthttp.Server
|
||||
paths []string
|
||||
isTLS bool
|
||||
|
@ -99,6 +116,16 @@ type ServerService struct {
|
|||
log *logrus.Entry
|
||||
}
|
||||
|
||||
// ServiceType returns the service type for this service, which is always 'server'.
|
||||
func (service *ServerService) ServiceType() string {
|
||||
return serviceTypeServer
|
||||
}
|
||||
|
||||
// ServiceName returns the individual name for this service.
|
||||
func (service *ServerService) ServiceName() string {
|
||||
return service.name
|
||||
}
|
||||
|
||||
// Run the ServerService.
|
||||
func (service *ServerService) Run() (err error) {
|
||||
defer func() {
|
||||
|
@ -120,13 +147,24 @@ func (service *ServerService) Run() (err error) {
|
|||
|
||||
// Shutdown the ServerService.
|
||||
func (service *ServerService) Shutdown() {
|
||||
if err := service.server.Shutdown(); err != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
|
||||
defer cancel()
|
||||
|
||||
if err := service.server.ShutdownWithContext(ctx); err != nil {
|
||||
service.log.WithError(err).Error("Error occurred during shutdown")
|
||||
}
|
||||
}
|
||||
|
||||
// Log returns the *logrus.Entry of the ServerService.
|
||||
func (service *ServerService) Log() *logrus.Entry {
|
||||
return service.log
|
||||
}
|
||||
|
||||
// FileWatcherService is a Service that watches files for changes.
|
||||
type FileWatcherService struct {
|
||||
name string
|
||||
|
||||
watcher *fsnotify.Watcher
|
||||
reload ProviderReload
|
||||
|
||||
|
@ -135,6 +173,16 @@ type FileWatcherService struct {
|
|||
directory string
|
||||
}
|
||||
|
||||
// ServiceType returns the service type for this service, which is always 'watcher'.
|
||||
func (service *FileWatcherService) ServiceType() string {
|
||||
return serviceTypeWatcher
|
||||
}
|
||||
|
||||
// ServiceName returns the individual name for this service.
|
||||
func (service *FileWatcherService) ServiceName() string {
|
||||
return service.name
|
||||
}
|
||||
|
||||
// Run the FileWatcherService.
|
||||
func (service *FileWatcherService) Run() (err error) {
|
||||
defer func() {
|
||||
|
@ -143,7 +191,7 @@ func (service *FileWatcherService) Run() (err error) {
|
|||
}
|
||||
}()
|
||||
|
||||
service.log.WithField("file", filepath.Join(service.directory, service.file)).Info("Watching for file changes to the file")
|
||||
service.log.WithField(logFieldFile, filepath.Join(service.directory, service.file)).Info("Watching file for changes")
|
||||
|
||||
for {
|
||||
select {
|
||||
|
@ -152,34 +200,36 @@ func (service *FileWatcherService) Run() (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
log := service.log.WithFields(map[string]any{logFieldFile: event.Name, logFieldOP: event.Op})
|
||||
|
||||
if service.file != "" && service.file != filepath.Base(event.Name) {
|
||||
service.log.WithFields(map[string]any{"file": event.Name, "op": event.Op}).Tracef("File modification detected to irrelevant file")
|
||||
log.Trace("File modification detected to irrelevant file")
|
||||
break
|
||||
}
|
||||
|
||||
switch {
|
||||
case event.Op&fsnotify.Write == fsnotify.Write, event.Op&fsnotify.Create == fsnotify.Create:
|
||||
service.log.WithFields(map[string]any{"file": event.Name, "op": event.Op}).Debug("File modification was detected")
|
||||
log.Debug("File modification was detected")
|
||||
|
||||
var reloaded bool
|
||||
|
||||
switch reloaded, err = service.reload.Reload(); {
|
||||
case err != nil:
|
||||
service.log.WithFields(map[string]any{"file": event.Name, "op": event.Op}).WithError(err).Error("Error occurred during reload")
|
||||
log.WithError(err).Error("Error occurred during reload")
|
||||
case reloaded:
|
||||
service.log.WithField("file", event.Name).Info("Reloaded successfully")
|
||||
log.Info("Reloaded successfully")
|
||||
default:
|
||||
service.log.WithField("file", event.Name).Debug("Reload of was triggered but it was skipped")
|
||||
log.Debug("Reload was triggered but it was skipped")
|
||||
}
|
||||
case event.Op&fsnotify.Remove == fsnotify.Remove:
|
||||
service.log.WithFields(map[string]any{"file": event.Name, "op": event.Op}).Debug("File remove was detected")
|
||||
log.Debug("File remove was detected")
|
||||
}
|
||||
case err, ok := <-service.watcher.Errors:
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
service.log.WithError(err).Errorf("Error while watching files")
|
||||
service.log.WithError(err).Error("Error while watching file for changes")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,6 +241,11 @@ func (service *FileWatcherService) Shutdown() {
|
|||
}
|
||||
}
|
||||
|
||||
// Log returns the *logrus.Entry of the FileWatcherService.
|
||||
func (service *FileWatcherService) Log() *logrus.Entry {
|
||||
return service.log
|
||||
}
|
||||
|
||||
func svcSvrMainFunc(ctx *CmdCtx) (service Service) {
|
||||
switch svr, listener, paths, isTLS, err := server.CreateDefaultServer(ctx.config, ctx.providers); {
|
||||
case err != nil:
|
||||
|
@ -267,34 +322,35 @@ func servicesRun(ctx *CmdCtx) {
|
|||
}
|
||||
}
|
||||
|
||||
ctx.log.Info("Startup Complete")
|
||||
ctx.log.Info("Startup complete")
|
||||
|
||||
select {
|
||||
case s := <-quit:
|
||||
switch s {
|
||||
case syscall.SIGINT:
|
||||
ctx.log.WithField("signal", "SIGINT").Debugf("Shutdown started due to signal")
|
||||
case syscall.SIGTERM:
|
||||
ctx.log.WithField("signal", "SIGTERM").Debugf("Shutdown started due to signal")
|
||||
}
|
||||
ctx.log.WithField("signal", s.String()).Debug("Shutdown initiated due to process signal")
|
||||
case <-cctx.Done():
|
||||
ctx.log.Debugf("Shutdown started due to context completion")
|
||||
ctx.log.Debug("Shutdown initiated due to context completion")
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
ctx.log.Infof("Shutting down")
|
||||
ctx.log.Info("Shutdown initiated")
|
||||
|
||||
wgShutdown := &sync.WaitGroup{}
|
||||
|
||||
ctx.log.Tracef("Shutdown of %d services is required", len(services))
|
||||
|
||||
for _, service := range services {
|
||||
go func() {
|
||||
wgShutdown.Add(1)
|
||||
|
||||
go func(service Service) {
|
||||
service.Log().Trace("Shutdown of service initiated")
|
||||
|
||||
service.Shutdown()
|
||||
|
||||
wgShutdown.Done()
|
||||
}()
|
||||
|
||||
wgShutdown.Add(1)
|
||||
service.Log().Trace("Shutdown of service complete")
|
||||
}(service)
|
||||
}
|
||||
|
||||
wgShutdown.Wait()
|
||||
|
@ -306,6 +362,8 @@ func servicesRun(ctx *CmdCtx) {
|
|||
}
|
||||
|
||||
if err = group.Wait(); err != nil {
|
||||
ctx.log.WithError(err).Errorf("Error occurred waiting for shutdown")
|
||||
ctx.log.WithError(err).Error("Error occurred waiting for shutdown")
|
||||
}
|
||||
|
||||
ctx.log.Info("Shutdown complete")
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ func waitUntilAutheliaBackendIsReady(dockerEnvironment *DockerEnvironment) error
|
|||
90*time.Second,
|
||||
dockerEnvironment,
|
||||
"authelia-backend",
|
||||
[]string{"Startup Complete"})
|
||||
[]string{"Startup complete"})
|
||||
}
|
||||
|
||||
func waitUntilAutheliaFrontendIsReady(dockerEnvironment *DockerEnvironment) error {
|
||||
|
|
|
@ -30,11 +30,11 @@
|
|||
"@fortawesome/free-solid-svg-icons": "6.3.0",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@mui/icons-material": "5.11.9",
|
||||
"@mui/material": "5.11.9",
|
||||
"@mui/material": "5.11.10",
|
||||
"@mui/styles": "5.11.9",
|
||||
"@simplewebauthn/browser": "7.1.0",
|
||||
"@simplewebauthn/typescript-types": "7.0.0",
|
||||
"axios": "1.3.3",
|
||||
"axios": "1.3.4",
|
||||
"broadcast-channel": "4.20.2",
|
||||
"classnames": "2.3.2",
|
||||
"i18next": "22.4.10",
|
||||
|
@ -43,7 +43,7 @@
|
|||
"qrcode.react": "3.1.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-i18next": "12.1.5",
|
||||
"react-i18next": "12.2.0",
|
||||
"react-loading": "2.0.3",
|
||||
"react-router-dom": "6.8.1",
|
||||
"react18-input-otp": "1.1.2",
|
||||
|
@ -154,15 +154,15 @@
|
|||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/react": "14.0.0",
|
||||
"@types/jest": "29.4.0",
|
||||
"@types/node": "18.14.0",
|
||||
"@types/node": "18.14.1",
|
||||
"@types/qrcode.react": "1.0.2",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"@types/zxcvbn": "4.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.52.0",
|
||||
"@typescript-eslint/parser": "5.52.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.53.0",
|
||||
"@typescript-eslint/parser": "5.53.0",
|
||||
"@vitejs/plugin-react": "3.1.0",
|
||||
"esbuild": "0.17.8",
|
||||
"esbuild": "0.17.10",
|
||||
"esbuild-jest": "0.5.0",
|
||||
"eslint": "8.34.0",
|
||||
"eslint-config-prettier": "8.6.0",
|
||||
|
@ -182,7 +182,7 @@
|
|||
"prettier": "2.8.4",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"typescript": "4.9.5",
|
||||
"vite": "4.1.2",
|
||||
"vite": "4.1.4",
|
||||
"vite-plugin-eslint": "1.8.1",
|
||||
"vite-plugin-istanbul": "4.0.0",
|
||||
"vite-plugin-svgr": "2.4.0",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue