feat(web): i18n asset overrides (#3040)
This allows overriding translation files in folders with lowercase RFC5646 / BCP47 Format language codes. This also fixes an issues where languages which don't expressly match the language code specified due to having a variant will also match the existing codes. Co-authored-by: Amir Zarrinkafsh <nightah@me.com>pull/3107/head^2
parent
ee9ce27ccf
commit
aac4c4772c
|
@ -34,15 +34,15 @@ logged for users.
|
||||||
### 4.30.0
|
### 4.30.0
|
||||||
The following changes occurred in 4.30.0:
|
The following changes occurred in 4.30.0:
|
||||||
|
|
||||||
|Previous Key |New Key |
|
| Previous Key | New Key |
|
||||||
|:-----------:|:--------------------:|
|
|:-------------:|:----------------------:|
|
||||||
|host |server.host |
|
| host | server.host |
|
||||||
|port |server.port |
|
| port | server.port |
|
||||||
|tls_key |server.tls.key |
|
| tls_key | server.tls.key |
|
||||||
|tls_cert |server.tls.certificate|
|
| tls_cert | server.tls.certificate |
|
||||||
|log_level |log.level |
|
| log_level | log.level |
|
||||||
|log_file_path|log.file_path |
|
| log_file_path | log.file_path |
|
||||||
|log_format |log.format |
|
| log_format | log.format |
|
||||||
|
|
||||||
_**Please Note:** you can no longer define secrets for providers that you are not using. For example if you're using the
|
_**Please Note:** you can no longer define secrets for providers that you are not using. For example if you're using the
|
||||||
[filesystem notifier](./notifier/filesystem.md) you must ensure that the `AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE`
|
[filesystem notifier](./notifier/filesystem.md) you must ensure that the `AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE`
|
||||||
|
|
|
@ -108,13 +108,15 @@ Example:
|
||||||
```console
|
```console
|
||||||
/config/assets/
|
/config/assets/
|
||||||
├── favicon.ico
|
├── favicon.ico
|
||||||
└── logo.png
|
├── logo.png
|
||||||
|
└── locales/<lang>[-[variant]]/<namespace>.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|Asset |File name|
|
| Asset | File name |
|
||||||
|:-----:|:---------------:|
|
|:-------:|:-------------:|
|
||||||
|Favicon|favicon.ico |
|
| Favicon | favicon.ico |
|
||||||
|Logo |logo.png |
|
| Logo | logo.png |
|
||||||
|
| locales | see [locales] |
|
||||||
|
|
||||||
### read_buffer_size
|
### read_buffer_size
|
||||||
<div markdown="1">
|
<div markdown="1">
|
||||||
|
@ -241,4 +243,44 @@ you're able to tune these individually depending on your needs.
|
||||||
### Asset Overrides
|
### Asset Overrides
|
||||||
|
|
||||||
If replacing the Logo for your Authelia portal it is recommended to upload a transparent PNG of your desired logo.
|
If replacing the Logo for your Authelia portal it is recommended to upload a transparent PNG of your desired logo.
|
||||||
Authelia will automatically resize the logo to an appropriate size to present in the frontend.
|
Authelia will automatically resize the logo to an appropriate size to present in the frontend.
|
||||||
|
|
||||||
|
#### locales
|
||||||
|
|
||||||
|
The locales folder holds folders of internationalization locales. This folder can be utilized to override these locales.
|
||||||
|
They are the names of locales that are returned by the `navigator.langauge` ECMAScript command. These are generally
|
||||||
|
those in the [RFC5646 / BCP47 Format](https://datatracker.ietf.org/doc/html/rfc5646) except the name normalized to
|
||||||
|
lowercase for consistency ease; for example the `en-US` locale should be in the directory `en-us`.
|
||||||
|
|
||||||
|
Each directory has json files which you can explore the format of in the
|
||||||
|
[internal/server/locales](https://github.com/authelia/authelia/tree/master/internal/server/locales) directory on
|
||||||
|
GitHub. The important part is the key names you wish to override. Each file represents a translation namespace. The list
|
||||||
|
of current namespaces are below:
|
||||||
|
|
||||||
|
| Namespace | Purpose |
|
||||||
|
|:---------:|:-------------------:|
|
||||||
|
| portal | Portal translations |
|
||||||
|
|
||||||
|
A full example for the `en-US` locale for the portal namespace is `locales/en-us/portal.json`.
|
||||||
|
|
||||||
|
Languages in browsers are supported in two forms. In their language only form such as `en` for English, and in their
|
||||||
|
variant form such as `en-AU` for English (Australian). If a user has the browser language `en-AU` we automatically load
|
||||||
|
the `en` and `en-AU` languages, where any keys in the `en-AU` language take precedence over the `en` language, and the
|
||||||
|
translations for the `en` language only applying when a translation from `en-AU` is not available.
|
||||||
|
|
||||||
|
List of supported languages and variants:
|
||||||
|
|
||||||
|
| Description | Language | Additional Variants | Location |
|
||||||
|
|:-----------:|:--------:|:-------------------:|:----------------------:|
|
||||||
|
| English | en | N/A | locales/en/portal.json |
|
||||||
|
| Spanish | es | N/A | locales/es/portal.json |
|
||||||
|
| German | de | N/A | locales/de/portal.json |
|
||||||
|
|
||||||
|
_**Important Note** Currently users can only override languages that already exist in this list either by overriding
|
||||||
|
the language itself, or adding a variant form of that language. If you'd like support for another language feel free
|
||||||
|
to make a PR. We also encourage people to make PR's for variants where the difference in the variants is important._
|
||||||
|
|
||||||
|
_**Important Note** Overriding these files will not guarantee any form of stability. Users who planning to utilize these
|
||||||
|
overrides should either check for changes to the files in the
|
||||||
|
[en](https://github.com/authelia/authelia/tree/master/internal/server/locales/en) translation prior to upgrading or PR
|
||||||
|
their translation to ensure it is maintained._
|
|
@ -2,28 +2,27 @@ package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AssetOverrideMiddleware allows overriding and serving of specific embedded assets from disk.
|
// AssetOverrideMiddleware allows overriding and serving of specific embedded assets from disk.
|
||||||
func AssetOverrideMiddleware(assetPath string, next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
func AssetOverrideMiddleware(root string, strip int, next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||||
return func(ctx *fasthttp.RequestCtx) {
|
return func(ctx *fasthttp.RequestCtx) {
|
||||||
uri := string(ctx.RequestURI())
|
if root == "" {
|
||||||
file := uri[strings.LastIndex(uri, "/")+1:]
|
|
||||||
|
|
||||||
if assetPath != "" && utils.IsStringInSlice(file, validOverrideAssets) {
|
|
||||||
_, err := os.Stat(assetPath + file)
|
|
||||||
if err != nil {
|
|
||||||
next(ctx)
|
|
||||||
} else {
|
|
||||||
fasthttp.FSHandler(assetPath, strings.Count(uri, "/")-1)(ctx)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
next(ctx)
|
next(ctx)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err := os.Stat(filepath.Join(root, string(fasthttp.NewPathSlashesStripper(strip)(ctx))))
|
||||||
|
if err != nil {
|
||||||
|
next(ctx)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fasthttp.FSHandler(root, strip)(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,5 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var protoHostSeparator = []byte("://")
|
var protoHostSeparator = []byte("://")
|
||||||
var validOverrideAssets = []string{"favicon.ico", "logo.png"}
|
|
||||||
|
|
||||||
var errPasswordPolicyNoMet = errors.New("the supplied password does not met the security policy")
|
var errPasswordPolicyNoMet = errors.New("the supplied password does not met the security policy")
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
"github.com/valyala/fasthttp/fasthttpadaptor"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed locales
|
||||||
|
var locales embed.FS
|
||||||
|
|
||||||
|
//go:embed public_html
|
||||||
|
var assets embed.FS
|
||||||
|
|
||||||
|
func newPublicHTMLEmbeddedHandler() fasthttp.RequestHandler {
|
||||||
|
embeddedPath, _ := fs.Sub(assets, "public_html")
|
||||||
|
|
||||||
|
return fasthttpadaptor.NewFastHTTPHandler(http.FileServer(http.FS(embeddedPath)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLocalesEmbeddedHandler() (handler fasthttp.RequestHandler) {
|
||||||
|
var languages []string
|
||||||
|
|
||||||
|
entries, err := locales.ReadDir("locales")
|
||||||
|
if err == nil {
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsDir() && len(entry.Name()) == 2 {
|
||||||
|
languages = append(languages, entry.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(ctx *fasthttp.RequestCtx) {
|
||||||
|
var (
|
||||||
|
language, variant, locale, namespace string
|
||||||
|
)
|
||||||
|
|
||||||
|
language = ctx.UserValue("language").(string)
|
||||||
|
namespace = ctx.UserValue("namespace").(string)
|
||||||
|
locale = language
|
||||||
|
|
||||||
|
if v := ctx.UserValue("variant"); v != nil {
|
||||||
|
variant = v.(string)
|
||||||
|
locale = fmt.Sprintf("%s-%s", language, locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
|
||||||
|
if data, err = locales.ReadFile(fmt.Sprintf("locales/%s/%s.json", locale, namespace)); err != nil {
|
||||||
|
if variant != "" && utils.IsStringInSliceFold(language, languages) {
|
||||||
|
data = []byte("{}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == 0 {
|
||||||
|
hfsHandleErr(ctx, err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetContentType("application/json")
|
||||||
|
ctx.SetBody(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hfsHandleErr(ctx *fasthttp.RequestCtx, err error) {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, fs.ErrNotExist):
|
||||||
|
writeStatus(ctx, fasthttp.StatusNotFound)
|
||||||
|
case errors.Is(err, fs.ErrPermission):
|
||||||
|
writeStatus(ctx, fasthttp.StatusForbidden)
|
||||||
|
default:
|
||||||
|
writeStatus(ctx, fasthttp.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeStatus(ctx *fasthttp.RequestCtx, status int) {
|
||||||
|
ctx.SetStatusCode(status)
|
||||||
|
ctx.SetBodyString(fmt.Sprintf("%d %s", status, fasthttp.StatusMessage(status)))
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
rootFiles = []string{"favicon.ico", "manifest.json", "robots.txt"}
|
rootFiles = []string{"manifest.json", "robots.txt"}
|
||||||
swaggerFiles = []string{
|
swaggerFiles = []string{
|
||||||
"favicon-16x16.png",
|
"favicon-16x16.png",
|
||||||
"favicon-32x32.png",
|
"favicon-32x32.png",
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"An email has been sent to your address to complete the process": "Es wurde eine E-Mail an Ihre Adresse geschickt, um den Vorgang abzuschließen.",
|
||||||
|
"Authenticated": "Authentifiziert",
|
||||||
|
"Cancel": "Abbrechen",
|
||||||
|
"Contact your administrator to register a device": "Wenden Sie sich an Ihren Administrator, um ein Gerät zu registrieren.",
|
||||||
|
"Could not obtain user settings": "Benutzereinstellungen konnten nicht abgerufen werden",
|
||||||
|
"Done": "Erledigt",
|
||||||
|
"Enter new password": "Neues Passwort eingeben",
|
||||||
|
"Enter one-time password": "Einmal-Passwort eingeben",
|
||||||
|
"Failed to register device, the provided link is expired or has already been used": "Gerät konnte nicht registriert werden, der angegebene Link ist abgelaufen oder wurde bereits verwendet",
|
||||||
|
"Hi": "Hallo",
|
||||||
|
"Incorrect username or password": "Falscher Benutzername oder falsches Passwort.",
|
||||||
|
"Loading": "Laden",
|
||||||
|
"Logout": "Abmelden",
|
||||||
|
"Lost your device?": "Haben Sie Ihr Gerät verloren?",
|
||||||
|
"Methods": "Verfahren",
|
||||||
|
"Need Google Authenticator?": "Benötigen Sie Google Authenticator?",
|
||||||
|
"New password": "Neues Passwort",
|
||||||
|
"No verification token provided": "Kein Verifizierungs-Token vorhanden",
|
||||||
|
"OTP Secret copied to clipboard": "OTP Secret in die Zwischenablage kopiert.",
|
||||||
|
"OTP URL copied to clipboard": "OTP-URL in die Zwischenablage kopiert.",
|
||||||
|
"One-Time Password": "One-Time-Passwort",
|
||||||
|
"Password has been reset": "Das Passwort wurde zurückgesetzt.",
|
||||||
|
"Password": "Passwort",
|
||||||
|
"Passwords do not match": "Die Passwörter stimmen nicht überein.",
|
||||||
|
"Push Notification": "Push-Benachrichtigung",
|
||||||
|
"Register device": "Gerät registrieren",
|
||||||
|
"Register your first device by clicking on the link below": "Registrieren Sie Ihr erstes Gerät, indem Sie auf den unten stehenden Link klicken.",
|
||||||
|
"Remember me": "Angemeldet bleiben",
|
||||||
|
"Repeat new password": "Neues Passwort wiederholen",
|
||||||
|
"Reset password": "Passwort zurücksetzen",
|
||||||
|
"Reset password?": "Passwort zurücksetzen?",
|
||||||
|
"Reset": "Zurücksetzen",
|
||||||
|
"Scan QR Code": "QR-Code scannen",
|
||||||
|
"Secret": "Geheimnis",
|
||||||
|
"Security Key - WebAuthN": "Sicherheitsschlüssel - WebAuthN",
|
||||||
|
"Select a Device": "Gerät auswählen",
|
||||||
|
"Sign in": "Anmelden",
|
||||||
|
"Sign out": "Abmelden",
|
||||||
|
"The resource you're attempting to access requires two-factor authentication": "Die Ressource, auf die Sie zuzugreifen versuchen, erfordert eine Zwei-Faktor-Authentifizierung.",
|
||||||
|
"There was a problem initiating the registration process": "Es gab ein Problem beim Starten des Registrierungsprozesses.",
|
||||||
|
"There was an issue completing the process. The verification token might have expired": "Es gab ein Problem beim Abschluss des Prozesses. Das Verifizierungs-Token könnte abgelaufen sein.",
|
||||||
|
"There was an issue initiating the password reset process": "Es gab ein Problem beim beim Starten des Passwortrücksetzprozesses.",
|
||||||
|
"There was an issue resetting the password": "Es gab ein Problem beim Zurücksetzen des Passworts",
|
||||||
|
"There was an issue signing out": "Es gab ein Problem bei der Abmeldung",
|
||||||
|
"Time-based One-Time Password": "Zeitbasiertes One-Time-Passwort",
|
||||||
|
"Username": "Benutzername",
|
||||||
|
"You must open the link from the same device and browser that initiated the registration process": "Sie müssen den Link mit demselben Gerät und demselben Browser öffnen, mit dem Sie den Registrierungsprozess gestartet haben.",
|
||||||
|
"You're being signed out and redirected": "Sie werden abgemeldet und umgeleitet",
|
||||||
|
"Your supplied password does not meet the password policy requirements": "Ihr angegebenes Passwort entspricht nicht den Anforderungen der Passwortrichtlinie.",
|
||||||
|
"Use OpenID to verify your identity": "Verwenden Sie OpenID, um Ihre Identität zu überprüfen",
|
||||||
|
"Access your display name": "Zugriff auf Ihren Anzeigenamen",
|
||||||
|
"Access your group membership": "Zugriff auf Ihre Gruppenmitgliedschaft",
|
||||||
|
"Access your email addresses": "Zugriff auf Ihre E-Mail-Adressen",
|
||||||
|
"Accept": "Annehmen",
|
||||||
|
"Deny": "Ablehnen",
|
||||||
|
"The above application is requesting the following permissions": "Die oben genannte Anwendung bittet um die folgenden Berechtigungen"
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
{
|
||||||
|
"An email has been sent to your address to complete the process": "An email has been sent to your address to complete the process.",
|
||||||
|
"Authenticated": "Authenticated",
|
||||||
|
"Cancel": "Cancel",
|
||||||
|
"Contact your administrator to register a device": "Contact your administrator to register a device.",
|
||||||
|
"Could not obtain user settings": "Could not obtain user settings",
|
||||||
|
"Done": "Done",
|
||||||
|
"Enter new password": "Enter new password",
|
||||||
|
"Enter one-time password": "Enter one-time password",
|
||||||
|
"Failed to register device, the provided link is expired or has already been used": "Failed to register device, the provided link is expired or has already been used",
|
||||||
|
"Hi": "Hi",
|
||||||
|
"Incorrect username or password": "Incorrect username or password.",
|
||||||
|
"Loading": "Loading",
|
||||||
|
"Logout": "Logout",
|
||||||
|
"Lost your device?": "Lost your device?",
|
||||||
|
"Methods": "Methods",
|
||||||
|
"Need Google Authenticator?": "Need Google Authenticator?",
|
||||||
|
"New password": "New password",
|
||||||
|
"No verification token provided": "No verification token provided",
|
||||||
|
"OTP Secret copied to clipboard": "OTP Secret copied to clipboard.",
|
||||||
|
"OTP URL copied to clipboard": "OTP URL copied to clipboard.",
|
||||||
|
"One-Time Password": "One-Time Password",
|
||||||
|
"Password has been reset": "Password has been reset.",
|
||||||
|
"Password": "Password",
|
||||||
|
"Passwords do not match": "Passwords do not match.",
|
||||||
|
"Push Notification": "Push Notification",
|
||||||
|
"Register device": "Register device",
|
||||||
|
"Register your first device by clicking on the link below": "Register your first device by clicking on the link below.",
|
||||||
|
"Remember me": "Remember me",
|
||||||
|
"Repeat new password": "Repeat new password",
|
||||||
|
"Reset password": "Reset password",
|
||||||
|
"Reset password?": "Reset password?",
|
||||||
|
"Reset": "Reset",
|
||||||
|
"Scan QR Code": "Scan QR Code",
|
||||||
|
"Secret": "Secret",
|
||||||
|
"Security Key - WebAuthN": "Security Key - WebAuthN",
|
||||||
|
"Select a Device": "Select a Device",
|
||||||
|
"Sign in": "Sign in",
|
||||||
|
"Sign out": "Sign out",
|
||||||
|
"The resource you're attempting to access requires two-factor authentication": "The resource you're attempting to access requires two-factor authentication.",
|
||||||
|
"There was a problem initiating the registration process": "There was a problem initiating the registration process",
|
||||||
|
"There was an issue completing the process. The verification token might have expired": "There was an issue completing the process. The verification token might have expired.",
|
||||||
|
"There was an issue initiating the password reset process": "There was an issue initiating the password reset process.",
|
||||||
|
"There was an issue resetting the password": "There was an issue resetting the password",
|
||||||
|
"There was an issue signing out": "There was an issue signing out",
|
||||||
|
"Time-based One-Time Password": "Time-based One-Time Password",
|
||||||
|
"Username": "Username",
|
||||||
|
"You must open the link from the same device and browser that initiated the registration process": "You must open the link from the same device and browser that initiated the registration process",
|
||||||
|
"You're being signed out and redirected": "You're being signed out and redirected",
|
||||||
|
"Your supplied password does not meet the password policy requirements": "Your supplied password does not meet the password policy requirements.",
|
||||||
|
"Use OpenID to verify your identity": "Use OpenID to verify your identity",
|
||||||
|
"Access your display name": "Access your display name",
|
||||||
|
"Access your group membership": "Access your group membership",
|
||||||
|
"Access your email addresses": "Access your email addresses",
|
||||||
|
"Accept": "Accept",
|
||||||
|
"Deny": "Deny",
|
||||||
|
"The above application is requesting the following permissions": "The above application is requesting the following permissions",
|
||||||
|
"The password does not meet the password policy": "The password does not meet the password policy",
|
||||||
|
"Must have at least one lowercase letter": "Must have at least one lowercase letter",
|
||||||
|
"Must have at least one UPPERCASE letter": "Must have at least one UPPERCASE letter",
|
||||||
|
"Must have at least one number": "Must have at least one number",
|
||||||
|
"Must have at least one special character": "Must have at least one special character",
|
||||||
|
"Must be at least {{len}} characters in length": "Must be at least {{len}} characters in length",
|
||||||
|
"Must not be more than {{len}} characters in length": "Must not be more than {{len}} characters in length"
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
{
|
||||||
|
"An email has been sent to your address to complete the process": "Un correo ha sido enviado a su cuenta para completar el proceso",
|
||||||
|
"Authenticated": "Autenticado",
|
||||||
|
"Cancel": "Cancelar",
|
||||||
|
"Contact your administrator to register a device": "Contacte a su administrador para registrar un dispositivo.",
|
||||||
|
"Could not obtain user settings": "Error al obtener configuración de usuario",
|
||||||
|
"Done": "Hecho",
|
||||||
|
"Enter new password": "Ingrese una nueva contraseña",
|
||||||
|
"Enter one-time password": "Ingrese contraseña de un solo uso (OTP)",
|
||||||
|
"Failed to register device, the provided link is expired or has already been used": "Error al registrar dispositivo, el link expiró o ya ha sido utilizado",
|
||||||
|
"Hi": "Hola",
|
||||||
|
"Incorrect username or password": "Usuario y/o contraseña incorrectos",
|
||||||
|
"Loading": "Cargando",
|
||||||
|
"Logout": "Cerrar Sesión",
|
||||||
|
"Lost your device?": "Perdió su dispositivo?",
|
||||||
|
"Methods": "Métodos",
|
||||||
|
"Need Google Authenticator?": "Necesita Google Authenticator?",
|
||||||
|
"New password": "Nueva contraseña",
|
||||||
|
"No verification token provided": "No se ha recibido el token de verificación",
|
||||||
|
"OTP Secret copied to clipboard": "La clave OTP ha sido copiada al portapapeles",
|
||||||
|
"OTP URL copied to clipboard": "la URL OTP ha sido copiada al portapapeles.",
|
||||||
|
"One-Time Password": "Contraseña de un solo uso (OTP)",
|
||||||
|
"Password has been reset": "La contraseña ha sido restablecida.",
|
||||||
|
"Password": "Contraseña",
|
||||||
|
"Passwords do not match": "Las contraseñas no coinciden.",
|
||||||
|
"Push Notification": "Notificaciones Push",
|
||||||
|
"Register device": "Registrar Dispositivo",
|
||||||
|
"Register your first device by clicking on the link below": "Registre su primer dispositivo, haciendo click en el siguiente link.",
|
||||||
|
"Remember me": "Recordarme",
|
||||||
|
"Repeat new password": "Repetir la contraseña",
|
||||||
|
"Reset password": "Restablecer Contraseña",
|
||||||
|
"Reset password?": "Olvidé mi contraseña",
|
||||||
|
"Reset": "Restablecer",
|
||||||
|
"Scan QR Code": "Escanear Código QR",
|
||||||
|
"Secret": "Secreto",
|
||||||
|
"Security Key - WebAuthN": "Llave de Seguridad - WebAuthN",
|
||||||
|
"Select a Device": "Seleccionar Dispositivo",
|
||||||
|
"Sign in": "Iniciar Sesión",
|
||||||
|
"Sign out": "Cerrar Sesión",
|
||||||
|
"The resource you're attempting to access requires two-factor authentication": "El recurso que intenta alcanzar requiere un segundo factor de autenticación (2FA).",
|
||||||
|
"There was a problem initiating the registration process": "Ocurrió un problema al iniciar el proceso de registración",
|
||||||
|
"There was an issue completing the process. The verification token might have expired": "Ocurrió un problema mientras se completaba el proceso. El token de verificación pudo haber expirado.",
|
||||||
|
"There was an issue initiating the password reset process": "Ha ocurrido un error al iniciar el proceso de proceso de restauración de contraseña.",
|
||||||
|
"There was an issue resetting the password": "Ocurrió un error al intentar restablecer la contraseña",
|
||||||
|
"There was an issue signing out": "Ocurrió un error al intentar cerrar sesión",
|
||||||
|
"Time-based One-Time Password": "Contraseña de uso único - OTP",
|
||||||
|
"Username": "Usuario",
|
||||||
|
"You must open the link from the same device and browser that initiated the registration process": "Debe abrir el link desde el mismo dispositivo y navegador desde el que inició el proceso de registración",
|
||||||
|
"You're being signed out and redirected": "Cerrando Sesión y redirigiendo",
|
||||||
|
"Your supplied password does not meet the password policy requirements": "La contraseña suministrada no cumple con los requerimientos de la política de contraseñas",
|
||||||
|
"Use OpenID to verify your identity": "Utilizar OpenID para verificar su identidad",
|
||||||
|
"Access your display name": "Acceso a su nombre",
|
||||||
|
"Access your group membership": "Acceso a su(s) grupo(s)",
|
||||||
|
"Access your email addresses": "Acceso a su dirección de correo",
|
||||||
|
"Accept": "Aceptar",
|
||||||
|
"Deny": "Denegar",
|
||||||
|
"The above application is requesting the following permissions": "La aplicación solicita los siguientes permisos",
|
||||||
|
"The password does not meet the password policy": "La contraseña no cumple con la política de contraseñas",
|
||||||
|
"Must have at least one lowercase letter": "Debe contener al menos una letra minúscula",
|
||||||
|
"Must have at least one UPPERCASE letter": "Debe contener al menos una letra MAYUSCULA",
|
||||||
|
"Must have at least one number": "Debe contener al menos un número",
|
||||||
|
"Must have at least one special character": "Debe contener al menos un caracter especial",
|
||||||
|
"Must be at least {{len}} characters in length": "La longitud mínima es de {{len}} caracteres",
|
||||||
|
"Must not be more than {{len}} characters in length": "La longitud máxima es de {{len}} caracteres"
|
||||||
|
}
|
|
@ -1,10 +1,7 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
|
||||||
"io/fs"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
@ -13,7 +10,6 @@ import (
|
||||||
"github.com/fasthttp/router"
|
"github.com/fasthttp/router"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
"github.com/valyala/fasthttp/expvarhandler"
|
"github.com/valyala/fasthttp/expvarhandler"
|
||||||
"github.com/valyala/fasthttp/fasthttpadaptor"
|
|
||||||
"github.com/valyala/fasthttp/pprofhandler"
|
"github.com/valyala/fasthttp/pprofhandler"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
@ -23,9 +19,6 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed public_html
|
|
||||||
var assets embed.FS
|
|
||||||
|
|
||||||
func registerRoutes(configuration schema.Configuration, providers middlewares.Providers) fasthttp.RequestHandler {
|
func registerRoutes(configuration schema.Configuration, providers middlewares.Providers) fasthttp.RequestHandler {
|
||||||
autheliaMiddleware := middlewares.AutheliaMiddleware(configuration, providers)
|
autheliaMiddleware := middlewares.AutheliaMiddleware(configuration, providers)
|
||||||
rememberMe := strconv.FormatBool(configuration.Session.RememberMeDuration != schema.RememberMeDisabled)
|
rememberMe := strconv.FormatBool(configuration.Session.RememberMeDuration != schema.RememberMeDisabled)
|
||||||
|
@ -36,8 +29,8 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr
|
||||||
duoSelfEnrollment = strconv.FormatBool(configuration.DuoAPI.EnableSelfEnrollment)
|
duoSelfEnrollment = strconv.FormatBool(configuration.DuoAPI.EnableSelfEnrollment)
|
||||||
}
|
}
|
||||||
|
|
||||||
embeddedPath, _ := fs.Sub(assets, "public_html")
|
handlerPublicHTML := newPublicHTMLEmbeddedHandler()
|
||||||
embeddedFS := fasthttpadaptor.NewFastHTTPHandler(http.FileServer(http.FS(embeddedPath)))
|
handlerLocales := newLocalesEmbeddedHandler()
|
||||||
|
|
||||||
https := configuration.Server.TLS.Key != "" && configuration.Server.TLS.Certificate != ""
|
https := configuration.Server.TLS.Key != "" && configuration.Server.TLS.Certificate != ""
|
||||||
|
|
||||||
|
@ -50,17 +43,22 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr
|
||||||
r.OPTIONS("/", autheliaMiddleware(handleOPTIONS))
|
r.OPTIONS("/", autheliaMiddleware(handleOPTIONS))
|
||||||
|
|
||||||
for _, f := range rootFiles {
|
for _, f := range rootFiles {
|
||||||
r.GET("/"+f, middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS))
|
r.GET("/"+f, handlerPublicHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.GET("/api/", autheliaMiddleware(serveSwaggerHandler))
|
r.GET("/api/", autheliaMiddleware(serveSwaggerHandler))
|
||||||
r.GET("/api/"+apiFile, autheliaMiddleware(serveSwaggerAPIHandler))
|
r.GET("/api/"+apiFile, autheliaMiddleware(serveSwaggerAPIHandler))
|
||||||
|
|
||||||
for _, file := range swaggerFiles {
|
for _, file := range swaggerFiles {
|
||||||
r.GET("/api/"+file, embeddedFS)
|
r.GET("/api/"+file, handlerPublicHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.GET("/static/{filepath:*}", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS))
|
r.GET("/favicon.ico", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, 0, handlerPublicHTML))
|
||||||
|
r.GET("/static/media/logo.png", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, 2, handlerPublicHTML))
|
||||||
|
r.GET("/static/{filepath:*}", handlerPublicHTML)
|
||||||
|
|
||||||
|
r.GET("/locales/{language:[a-z]{1,3}}-{variant:[a-z0-9-]+}/{namespace:[a-z]+}.json", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, 0, handlerLocales))
|
||||||
|
r.GET("/locales/{language:[a-z]{1,3}}/{namespace:[a-z]+}.json", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, 0, handlerLocales))
|
||||||
|
|
||||||
r.GET("/api/health", autheliaMiddleware(handlers.HealthGet))
|
r.GET("/api/health", autheliaMiddleware(handlers.HealthGet))
|
||||||
r.GET("/api/state", autheliaMiddleware(handlers.StateGet))
|
r.GET("/api/state", autheliaMiddleware(handlers.StateGet))
|
||||||
|
|
|
@ -43,7 +43,7 @@ func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberM
|
||||||
logoOverride := f
|
logoOverride := f
|
||||||
|
|
||||||
if assetPath != "" {
|
if assetPath != "" {
|
||||||
if _, err := os.Stat(assetPath + logoFile); err == nil {
|
if _, err := os.Stat(filepath.Join(assetPath, logoFile)); err == nil {
|
||||||
logoOverride = t
|
logoOverride = t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, Suspense } from "react";
|
||||||
|
|
||||||
import { config as faConfig } from "@fortawesome/fontawesome-svg-core";
|
import { config as faConfig } from "@fortawesome/fontawesome-svg-core";
|
||||||
import { CssBaseline, ThemeProvider } from "@material-ui/core";
|
import { CssBaseline, ThemeProvider } from "@material-ui/core";
|
||||||
|
@ -21,6 +21,7 @@ import { getBasePath } from "@utils/BasePath";
|
||||||
import { getDuoSelfEnrollment, getRememberMe, getResetPassword, getTheme } from "@utils/Configuration";
|
import { getDuoSelfEnrollment, getRememberMe, getResetPassword, getTheme } from "@utils/Configuration";
|
||||||
import RegisterOneTimePassword from "@views/DeviceRegistration/RegisterOneTimePassword";
|
import RegisterOneTimePassword from "@views/DeviceRegistration/RegisterOneTimePassword";
|
||||||
import RegisterWebauthn from "@views/DeviceRegistration/RegisterWebauthn";
|
import RegisterWebauthn from "@views/DeviceRegistration/RegisterWebauthn";
|
||||||
|
import BaseLoadingPage from "@views/LoadingPage/BaseLoadingPage";
|
||||||
import ConsentView from "@views/LoginPortal/ConsentView/ConsentView";
|
import ConsentView from "@views/LoginPortal/ConsentView/ConsentView";
|
||||||
import LoginPortal from "@views/LoginPortal/LoginPortal";
|
import LoginPortal from "@views/LoginPortal/LoginPortal";
|
||||||
import SignOut from "@views/LoginPortal/SignOut/SignOut";
|
import SignOut from "@views/LoginPortal/SignOut/SignOut";
|
||||||
|
@ -60,30 +61,32 @@ const App: React.FC = () => {
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<Suspense fallback={<BaseLoadingPage message={"Loading"} />}>
|
||||||
<NotificationsContext.Provider value={{ notification, setNotification }}>
|
<CssBaseline />
|
||||||
<Router basename={getBasePath()}>
|
<NotificationsContext.Provider value={{ notification, setNotification }}>
|
||||||
<NotificationBar onClose={() => setNotification(null)} />
|
<Router basename={getBasePath()}>
|
||||||
<Routes>
|
<NotificationBar onClose={() => setNotification(null)} />
|
||||||
<Route path={ResetPasswordStep1Route} element={<ResetPasswordStep1 />} />
|
<Routes>
|
||||||
<Route path={ResetPasswordStep2Route} element={<ResetPasswordStep2 />} />
|
<Route path={ResetPasswordStep1Route} element={<ResetPasswordStep1 />} />
|
||||||
<Route path={RegisterWebauthnRoute} element={<RegisterWebauthn />} />
|
<Route path={ResetPasswordStep2Route} element={<ResetPasswordStep2 />} />
|
||||||
<Route path={RegisterOneTimePasswordRoute} element={<RegisterOneTimePassword />} />
|
<Route path={RegisterWebauthnRoute} element={<RegisterWebauthn />} />
|
||||||
<Route path={LogoutRoute} element={<SignOut />} />
|
<Route path={RegisterOneTimePasswordRoute} element={<RegisterOneTimePassword />} />
|
||||||
<Route path={ConsentRoute} element={<ConsentView />} />
|
<Route path={LogoutRoute} element={<SignOut />} />
|
||||||
<Route
|
<Route path={ConsentRoute} element={<ConsentView />} />
|
||||||
path={`${IndexRoute}*`}
|
<Route
|
||||||
element={
|
path={`${IndexRoute}*`}
|
||||||
<LoginPortal
|
element={
|
||||||
duoSelfEnrollment={getDuoSelfEnrollment()}
|
<LoginPortal
|
||||||
rememberMe={getRememberMe()}
|
duoSelfEnrollment={getDuoSelfEnrollment()}
|
||||||
resetPassword={getResetPassword()}
|
rememberMe={getRememberMe()}
|
||||||
/>
|
resetPassword={getResetPassword()}
|
||||||
}
|
/>
|
||||||
/>
|
}
|
||||||
</Routes>
|
/>
|
||||||
</Router>
|
</Routes>
|
||||||
</NotificationsContext.Provider>
|
</Router>
|
||||||
|
</NotificationsContext.Provider>
|
||||||
|
</Suspense>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,33 +1,28 @@
|
||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
import XHR from "i18next-http-backend";
|
import Backend from "i18next-http-backend";
|
||||||
import { initReactI18next } from "react-i18next";
|
import { initReactI18next } from "react-i18next";
|
||||||
|
|
||||||
import langDe from "@i18n/locales/de.json";
|
i18n.use(Backend)
|
||||||
import langEn from "@i18n/locales/en.json";
|
|
||||||
import langEs from "@i18n/locales/es.json";
|
|
||||||
|
|
||||||
const resources = {
|
|
||||||
en: langEn,
|
|
||||||
es: langEs,
|
|
||||||
de: langDe,
|
|
||||||
};
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
order: ["querystring", "navigator"],
|
|
||||||
lookupQuerystring: "lng",
|
|
||||||
};
|
|
||||||
|
|
||||||
i18n.use(XHR)
|
|
||||||
.use(LanguageDetector)
|
.use(LanguageDetector)
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.init({
|
.init({
|
||||||
detection: options,
|
detection: {
|
||||||
resources,
|
order: ["querystring", "navigator"],
|
||||||
ns: [""],
|
lookupQuerystring: "lng",
|
||||||
defaultNS: "",
|
},
|
||||||
fallbackLng: "en",
|
backend: {
|
||||||
|
loadPath: "/locales/{{lng}}/{{ns}}.json",
|
||||||
|
},
|
||||||
|
ns: ["portal"],
|
||||||
|
defaultNS: "portal",
|
||||||
|
fallbackLng: {
|
||||||
|
default: ["en"],
|
||||||
|
},
|
||||||
|
load: "all",
|
||||||
supportedLngs: ["en", "es", "de"],
|
supportedLngs: ["en", "es", "de"],
|
||||||
|
lowerCaseLng: true,
|
||||||
|
nonExplicitSupportedLngs: true,
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false,
|
escapeValue: false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
{
|
|
||||||
"Portal": {
|
|
||||||
"An email has been sent to your address to complete the process": "Es wurde eine E-Mail an Ihre Adresse geschickt, um den Vorgang abzuschließen.",
|
|
||||||
"Authenticated": "Authentifiziert",
|
|
||||||
"Cancel": "Abbrechen",
|
|
||||||
"Contact your administrator to register a device": "Wenden Sie sich an Ihren Administrator, um ein Gerät zu registrieren.",
|
|
||||||
"Could not obtain user settings": "Benutzereinstellungen konnten nicht abgerufen werden",
|
|
||||||
"Done": "Erledigt",
|
|
||||||
"Enter new password": "Neues Passwort eingeben",
|
|
||||||
"Enter one-time password": "Einmal-Passwort eingeben",
|
|
||||||
"Failed to register device, the provided link is expired or has already been used": "Gerät konnte nicht registriert werden, der angegebene Link ist abgelaufen oder wurde bereits verwendet",
|
|
||||||
"Hi": "Hallo",
|
|
||||||
"Incorrect username or password": "Falscher Benutzername oder falsches Passwort.",
|
|
||||||
"Loading": "Laden",
|
|
||||||
"Logout": "Abmelden",
|
|
||||||
"Lost your device?": "Haben Sie Ihr Gerät verloren?",
|
|
||||||
"Methods": "Verfahren",
|
|
||||||
"Need Google Authenticator?": "Benötigen Sie Google Authenticator?",
|
|
||||||
"New password": "Neues Passwort",
|
|
||||||
"No verification token provided": "Kein Verifizierungs-Token vorhanden",
|
|
||||||
"OTP Secret copied to clipboard": "OTP Secret in die Zwischenablage kopiert.",
|
|
||||||
"OTP URL copied to clipboard": "OTP-URL in die Zwischenablage kopiert.",
|
|
||||||
"One-Time Password": "One-Time-Passwort",
|
|
||||||
"Password has been reset": "Das Passwort wurde zurückgesetzt.",
|
|
||||||
"Password": "Passwort",
|
|
||||||
"Passwords do not match": "Die Passwörter stimmen nicht überein.",
|
|
||||||
"Push Notification": "Push-Benachrichtigung",
|
|
||||||
"Register device": "Gerät registrieren",
|
|
||||||
"Register your first device by clicking on the link below": "Registrieren Sie Ihr erstes Gerät, indem Sie auf den unten stehenden Link klicken.",
|
|
||||||
"Remember me": "Angemeldet bleiben",
|
|
||||||
"Repeat new password": "Neues Passwort wiederholen",
|
|
||||||
"Reset password": "Passwort zurücksetzen",
|
|
||||||
"Reset password?": "Passwort zurücksetzen?",
|
|
||||||
"Reset": "Zurücksetzen",
|
|
||||||
"Scan QR Code": "QR-Code scannen",
|
|
||||||
"Secret": "Geheimnis",
|
|
||||||
"Security Key - WebAuthN": "Sicherheitsschlüssel - WebAuthN",
|
|
||||||
"Select a Device": "Gerät auswählen",
|
|
||||||
"Sign in": "Anmelden",
|
|
||||||
"Sign out": "Abmelden",
|
|
||||||
"The resource you're attempting to access requires two-factor authentication": "Die Ressource, auf die Sie zuzugreifen versuchen, erfordert eine Zwei-Faktor-Authentifizierung.",
|
|
||||||
"There was a problem initiating the registration process": "Es gab ein Problem beim Starten des Registrierungsprozesses.",
|
|
||||||
"There was an issue completing the process. The verification token might have expired": "Es gab ein Problem beim Abschluss des Prozesses. Das Verifizierungs-Token könnte abgelaufen sein.",
|
|
||||||
"There was an issue initiating the password reset process": "Es gab ein Problem beim beim Starten des Passwortrücksetzprozesses.",
|
|
||||||
"There was an issue resetting the password": "Es gab ein Problem beim Zurücksetzen des Passworts",
|
|
||||||
"There was an issue signing out": "Es gab ein Problem bei der Abmeldung",
|
|
||||||
"Time-based One-Time Password": "Zeitbasiertes One-Time-Passwort",
|
|
||||||
"Username": "Benutzername",
|
|
||||||
"You must open the link from the same device and browser that initiated the registration process": "Sie müssen den Link mit demselben Gerät und demselben Browser öffnen, mit dem Sie den Registrierungsprozess gestartet haben.",
|
|
||||||
"You're being signed out and redirected": "Sie werden abgemeldet und umgeleitet",
|
|
||||||
"Your supplied password does not meet the password policy requirements": "Ihr angegebenes Passwort entspricht nicht den Anforderungen der Passwortrichtlinie.",
|
|
||||||
"Use OpenID to verify your identity": "Verwenden Sie OpenID, um Ihre Identität zu überprüfen",
|
|
||||||
"Access your display name": "Zugriff auf Ihren Anzeigenamen",
|
|
||||||
"Access your group membership": "Zugriff auf Ihre Gruppenmitgliedschaft",
|
|
||||||
"Access your email addresses": "Zugriff auf Ihre E-Mail-Adressen",
|
|
||||||
"Accept": "Annehmen",
|
|
||||||
"Deny": "Ablehnen",
|
|
||||||
"The above application is requesting the following permissions": "Die oben genannte Anwendung bittet um die folgenden Berechtigungen"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
{
|
|
||||||
"Portal": {
|
|
||||||
"An email has been sent to your address to complete the process": "An email has been sent to your address to complete the process.",
|
|
||||||
"Authenticated": "Authenticated",
|
|
||||||
"Cancel": "Cancel",
|
|
||||||
"Contact your administrator to register a device": "Contact your administrator to register a device.",
|
|
||||||
"Could not obtain user settings": "Could not obtain user settings",
|
|
||||||
"Done": "Done",
|
|
||||||
"Enter new password": "Enter new password",
|
|
||||||
"Enter one-time password": "Enter one-time password",
|
|
||||||
"Failed to register device, the provided link is expired or has already been used": "Failed to register device, the provided link is expired or has already been used",
|
|
||||||
"Hi": "Hi",
|
|
||||||
"Incorrect username or password": "Incorrect username or password.",
|
|
||||||
"Loading": "Loading",
|
|
||||||
"Logout": "Logout",
|
|
||||||
"Lost your device?": "Lost your device?",
|
|
||||||
"Methods": "Methods",
|
|
||||||
"Need Google Authenticator?": "Need Google Authenticator?",
|
|
||||||
"New password": "New password",
|
|
||||||
"No verification token provided": "No verification token provided",
|
|
||||||
"OTP Secret copied to clipboard": "OTP Secret copied to clipboard.",
|
|
||||||
"OTP URL copied to clipboard": "OTP URL copied to clipboard.",
|
|
||||||
"One-Time Password": "One-Time Password",
|
|
||||||
"Password has been reset": "Password has been reset.",
|
|
||||||
"Password": "Password",
|
|
||||||
"Passwords do not match": "Passwords do not match.",
|
|
||||||
"Push Notification": "Push Notification",
|
|
||||||
"Register device": "Register device",
|
|
||||||
"Register your first device by clicking on the link below": "Register your first device by clicking on the link below.",
|
|
||||||
"Remember me": "Remember me",
|
|
||||||
"Repeat new password": "Repeat new password",
|
|
||||||
"Reset password": "Reset password",
|
|
||||||
"Reset password?": "Reset password?",
|
|
||||||
"Reset": "Reset",
|
|
||||||
"Scan QR Code": "Scan QR Code",
|
|
||||||
"Secret": "Secret",
|
|
||||||
"Security Key - WebAuthN": "Security Key - WebAuthN",
|
|
||||||
"Select a Device": "Select a Device",
|
|
||||||
"Sign in": "Sign in",
|
|
||||||
"Sign out": "Sign out",
|
|
||||||
"The resource you're attempting to access requires two-factor authentication": "The resource you're attempting to access requires two-factor authentication.",
|
|
||||||
"There was a problem initiating the registration process": "There was a problem initiating the registration process",
|
|
||||||
"There was an issue completing the process. The verification token might have expired": "There was an issue completing the process. The verification token might have expired.",
|
|
||||||
"There was an issue initiating the password reset process": "There was an issue initiating the password reset process.",
|
|
||||||
"There was an issue resetting the password": "There was an issue resetting the password",
|
|
||||||
"There was an issue signing out": "There was an issue signing out",
|
|
||||||
"Time-based One-Time Password": "Time-based One-Time Password",
|
|
||||||
"Username": "Username",
|
|
||||||
"You must open the link from the same device and browser that initiated the registration process": "You must open the link from the same device and browser that initiated the registration process",
|
|
||||||
"You're being signed out and redirected": "You're being signed out and redirected",
|
|
||||||
"Your supplied password does not meet the password policy requirements": "Your supplied password does not meet the password policy requirements.",
|
|
||||||
"Use OpenID to verify your identity": "Use OpenID to verify your identity",
|
|
||||||
"Access your display name": "Access your display name",
|
|
||||||
"Access your group membership": "Access your group membership",
|
|
||||||
"Access your email addresses": "Access your email addresses",
|
|
||||||
"Accept": "Accept",
|
|
||||||
"Deny": "Deny",
|
|
||||||
"The above application is requesting the following permissions": "The above application is requesting the following permissions",
|
|
||||||
"The password does not meet the password policy": "The password does not meet the password policy",
|
|
||||||
"Must have at least one lowercase letter": "Must have at least one lowercase letter",
|
|
||||||
"Must have at least one UPPERCASE letter": "Must have at least one UPPERCASE letter",
|
|
||||||
"Must have at least one number": "Must have at least one number",
|
|
||||||
"Must have at least one special character": "Must have at least one special character",
|
|
||||||
"Must be at least {{len}} characters in length": "Must be at least {{len}} characters in length",
|
|
||||||
"Must not be more than {{len}} characters in length": "Must not be more than {{len}} characters in length"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
{
|
|
||||||
"Portal": {
|
|
||||||
"An email has been sent to your address to complete the process": "Un correo ha sido enviado a su cuenta para completar el proceso",
|
|
||||||
"Authenticated": "Autenticado",
|
|
||||||
"Cancel": "Cancelar",
|
|
||||||
"Contact your administrator to register a device": "Contacte a su administrador para registrar un dispositivo.",
|
|
||||||
"Could not obtain user settings": "Error al obtener configuración de usuario",
|
|
||||||
"Done": "Hecho",
|
|
||||||
"Enter new password": "Ingrese una nueva contraseña",
|
|
||||||
"Enter one-time password": "Ingrese contraseña de un solo uso (OTP)",
|
|
||||||
"Failed to register device, the provided link is expired or has already been used": "Error al registrar dispositivo, el link expiró o ya ha sido utilizado",
|
|
||||||
"Hi": "Hola",
|
|
||||||
"Incorrect username or password": "Usuario y/o contraseña incorrectos",
|
|
||||||
"Loading": "Cargando",
|
|
||||||
"Logout": "Cerrar Sesión",
|
|
||||||
"Lost your device?": "Perdió su dispositivo?",
|
|
||||||
"Methods": "Métodos",
|
|
||||||
"Need Google Authenticator?": "Necesita Google Authenticator?",
|
|
||||||
"New password": "Nueva contraseña",
|
|
||||||
"No verification token provided": "No se ha recibido el token de verificación",
|
|
||||||
"OTP Secret copied to clipboard": "La clave OTP ha sido copiada al portapapeles",
|
|
||||||
"OTP URL copied to clipboard": "la URL OTP ha sido copiada al portapapeles.",
|
|
||||||
"One-Time Password": "Contraseña de un solo uso (OTP)",
|
|
||||||
"Password has been reset": "La contraseña ha sido restablecida.",
|
|
||||||
"Password": "Contraseña",
|
|
||||||
"Passwords do not match": "Las contraseñas no coinciden.",
|
|
||||||
"Push Notification": "Notificaciones Push",
|
|
||||||
"Register device": "Registrar Dispositivo",
|
|
||||||
"Register your first device by clicking on the link below": "Registre su primer dispositivo, haciendo click en el siguiente link.",
|
|
||||||
"Remember me": "Recordarme",
|
|
||||||
"Repeat new password": "Repetir la contraseña",
|
|
||||||
"Reset password": "Restablecer Contraseña",
|
|
||||||
"Reset password?": "Olvidé mi contraseña",
|
|
||||||
"Reset": "Restablecer",
|
|
||||||
"Scan QR Code": "Escanear Código QR",
|
|
||||||
"Secret": "Secreto",
|
|
||||||
"Security Key - WebAuthN": "Llave de Seguridad - WebAuthN",
|
|
||||||
"Select a Device": "Seleccionar Dispositivo",
|
|
||||||
"Sign in": "Iniciar Sesión",
|
|
||||||
"Sign out": "Cerrar Sesión",
|
|
||||||
"The resource you're attempting to access requires two-factor authentication": "El recurso que intenta alcanzar requiere un segundo factor de autenticación (2FA).",
|
|
||||||
"There was a problem initiating the registration process": "Ocurrió un problema al iniciar el proceso de registración",
|
|
||||||
"There was an issue completing the process. The verification token might have expired": "Ocurrió un problema mientras se completaba el proceso. El token de verificación pudo haber expirado.",
|
|
||||||
"There was an issue initiating the password reset process": "Ha ocurrido un error al iniciar el proceso de proceso de restauración de contraseña.",
|
|
||||||
"There was an issue resetting the password": "Ocurrió un error al intentar restablecer la contraseña",
|
|
||||||
"There was an issue signing out": "Ocurrió un error al intentar cerrar sesión",
|
|
||||||
"Time-based One-Time Password": "Contraseña de uso único - OTP",
|
|
||||||
"Username": "Usuario",
|
|
||||||
"You must open the link from the same device and browser that initiated the registration process": "Debe abrir el link desde el mismo dispositivo y navegador desde el que inició el proceso de registración",
|
|
||||||
"You're being signed out and redirected": "Cerrando Sesión y redirigiendo",
|
|
||||||
"Your supplied password does not meet the password policy requirements": "La contraseña suministrada no cumple con los requerimientos de la política de contraseñas",
|
|
||||||
"Use OpenID to verify your identity": "Utilizar OpenID para verificar su identidad",
|
|
||||||
"Access your display name": "Acceso a su nombre",
|
|
||||||
"Access your group membership": "Acceso a su(s) grupo(s)",
|
|
||||||
"Access your email addresses": "Acceso a su dirección de correo",
|
|
||||||
"Accept": "Aceptar",
|
|
||||||
"Deny": "Denegar",
|
|
||||||
"The above application is requesting the following permissions": "La aplicación solicita los siguientes permisos",
|
|
||||||
"The password does not meet the password policy": "La contraseña no cumple con la política de contraseñas",
|
|
||||||
"Must have at least one lowercase letter": "Debe contener al menos una letra minúscula",
|
|
||||||
"Must have at least one UPPERCASE letter": "Debe contener al menos una letra MAYUSCULA",
|
|
||||||
"Must have at least one number": "Debe contener al menos un número",
|
|
||||||
"Must have at least one special character": "Debe contener al menos un caracter especial",
|
|
||||||
"Must be at least {{len}} characters in length": "La longitud mínima es de {{len}} caracteres",
|
|
||||||
"Must not be more than {{len}} characters in length": "La longitud máxima es de {{len}} caracteres"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,7 +27,7 @@ const RegisterOneTimePassword = function () {
|
||||||
const { createSuccessNotification, createErrorNotification } = useNotifications();
|
const { createSuccessNotification, createErrorNotification } = useNotifications();
|
||||||
const [hasErrored, setHasErrored] = useState(false);
|
const [hasErrored, setHasErrored] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { t: translate } = useTranslation("Portal");
|
const { t: translate } = useTranslation();
|
||||||
|
|
||||||
// Get the token from the query param to give it back to the API when requesting
|
// Get the token from the query param to give it back to the API when requesting
|
||||||
// the secret for OTP.
|
// the secret for OTP.
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { Grid, Typography, useTheme } from "@material-ui/core";
|
||||||
|
import ReactLoading from "react-loading";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BaseLoadingPage = function (props: Props) {
|
||||||
|
const theme = useTheme();
|
||||||
|
return (
|
||||||
|
<Grid container alignItems="center" justifyContent="center" style={{ minHeight: "100vh" }}>
|
||||||
|
<Grid item style={{ textAlign: "center", display: "inline-block" }}>
|
||||||
|
<ReactLoading width={64} height={64} color={theme.custom.loadingBar} type="bars" />
|
||||||
|
<Typography>{props.message}...</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BaseLoadingPage;
|
|
@ -1,20 +1,12 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { useTheme, Typography, Grid } from "@material-ui/core";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import ReactLoading from "react-loading";
|
|
||||||
|
import BaseLoadingPage from "@views/LoadingPage/BaseLoadingPage";
|
||||||
|
|
||||||
const LoadingPage = function () {
|
const LoadingPage = function () {
|
||||||
const theme = useTheme();
|
const { t: translate } = useTranslation();
|
||||||
const { t: translate } = useTranslation("Portal");
|
return <BaseLoadingPage message={translate("Loading")} />;
|
||||||
return (
|
|
||||||
<Grid container alignItems="center" justifyContent="center" style={{ minHeight: "100vh" }}>
|
|
||||||
<Grid item style={{ textAlign: "center", display: "inline-block" }}>
|
|
||||||
<ReactLoading width={64} height={64} color={theme.custom.loadingBar} type="bars" />
|
|
||||||
<Typography>{translate("Loading")}...</Typography>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LoadingPage;
|
export default LoadingPage;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import SuccessIcon from "@components/SuccessIcon";
|
||||||
|
|
||||||
const Authenticated = function () {
|
const Authenticated = function () {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { t: translate } = useTranslation("Portal");
|
const { t: translate } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div id="authenticated-stage">
|
<div id="authenticated-stage">
|
||||||
<div className={classes.iconContainer}>
|
<div className={classes.iconContainer}>
|
||||||
|
|
|
@ -15,7 +15,7 @@ export interface Props {
|
||||||
const AuthenticatedView = function (props: Props) {
|
const AuthenticatedView = function (props: Props) {
|
||||||
const style = useStyles();
|
const style = useStyles();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t: translate } = useTranslation("Portal");
|
const { t: translate } = useTranslation();
|
||||||
|
|
||||||
const handleLogoutClick = () => {
|
const handleLogoutClick = () => {
|
||||||
navigate(SignOutRoute);
|
navigate(SignOutRoute);
|
||||||
|
|
|
@ -46,7 +46,7 @@ const ConsentView = function (props: Props) {
|
||||||
const redirect = useRedirector();
|
const redirect = useRedirector();
|
||||||
const { createErrorNotification, resetNotification } = useNotifications();
|
const { createErrorNotification, resetNotification } = useNotifications();
|
||||||
const [resp, fetch, , err] = useRequestedScopes();
|
const [resp, fetch, , err] = useRequestedScopes();
|
||||||
const { t: translate } = useTranslation("Portal");
|
const { t: translate } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|
|
@ -38,7 +38,7 @@ const FirstFactorForm = function (props: Props) {
|
||||||
// TODO (PR: #806, Issue: #511) potentially refactor
|
// TODO (PR: #806, Issue: #511) potentially refactor
|
||||||
const usernameRef = useRef() as MutableRefObject<HTMLInputElement>;
|
const usernameRef = useRef() as MutableRefObject<HTMLInputElement>;
|
||||||
const passwordRef = useRef() as MutableRefObject<HTMLInputElement>;
|
const passwordRef = useRef() as MutableRefObject<HTMLInputElement>;
|
||||||
const { t: translate } = useTranslation("Portal");
|
const { t: translate } = useTranslation();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeout = setTimeout(() => usernameRef.current.focus(), 10);
|
const timeout = setTimeout(() => usernameRef.current.focus(), 10);
|
||||||
return () => clearTimeout(timeout);
|
return () => clearTimeout(timeout);
|
||||||
|
|
|
@ -28,7 +28,7 @@ export interface Props {
|
||||||
|
|
||||||
const DefaultMethodContainer = function (props: Props) {
|
const DefaultMethodContainer = function (props: Props) {
|
||||||
const style = useStyles();
|
const style = useStyles();
|
||||||
const { t: translate } = useTranslation("Portal");
|
const { t: translate } = useTranslation();
|
||||||
const registerMessage = props.registered
|
const registerMessage = props.registered
|
||||||
? props.title === "Push Notification"
|
? props.title === "Push Notification"
|
||||||
? ""
|
? ""
|
||||||
|
@ -97,7 +97,7 @@ interface NotRegisteredContainerProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function NotRegisteredContainer(props: NotRegisteredContainerProps) {
|
function NotRegisteredContainer(props: NotRegisteredContainerProps) {
|
||||||
const { t: translate } = useTranslation("Portal");
|
const { t: translate } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|
|
@ -29,7 +29,7 @@ export interface Props {
|
||||||
const MethodSelectionDialog = function (props: Props) {
|
const MethodSelectionDialog = function (props: Props) {
|
||||||
const style = useStyles();
|
const style = useStyles();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t: translate } = useTranslation("Portal");
|
const { t: translate } = useTranslation();
|
||||||
|
|
||||||
const pieChartIcon = (
|
const pieChartIcon = (
|
||||||
<TimerIcon width={24} height={24} period={15} color={theme.palette.primary.main} backgroundColor={"white"} />
|
<TimerIcon width={24} height={24} period={15} color={theme.palette.primary.main} backgroundColor={"white"} />
|
||||||
|
|
|
@ -33,7 +33,7 @@ const OneTimePasswordMethod = function (props: Props) {
|
||||||
props.authenticationLevel === AuthenticationLevel.TwoFactor ? State.Success : State.Idle,
|
props.authenticationLevel === AuthenticationLevel.TwoFactor ? State.Success : State.Idle,
|
||||||
);
|
);
|
||||||
const redirectionURL = useRedirectionURL();
|
const redirectionURL = useRedirectionURL();
|
||||||
const { t: translate } = useTranslation("Portal");
|
const { t: translate } = useTranslation();
|
||||||
|
|
||||||
const { onSignInSuccess, onSignInError } = props;
|
const { onSignInSuccess, onSignInError } = props;
|
||||||
const onSignInErrorCallback = useRef(onSignInError).current;
|
const onSignInErrorCallback = useRef(onSignInError).current;
|
||||||
|
|
|
@ -41,7 +41,7 @@ const SecondFactorForm = function (props: Props) {
|
||||||
const { createInfoNotification, createErrorNotification } = useNotifications();
|
const { createInfoNotification, createErrorNotification } = useNotifications();
|
||||||
const [registrationInProgress, setRegistrationInProgress] = useState(false);
|
const [registrationInProgress, setRegistrationInProgress] = useState(false);
|
||||||
const [webauthnSupported, setWebauthnSupported] = useState(false);
|
const [webauthnSupported, setWebauthnSupported] = useState(false);
|
||||||
const { t: translate } = useTranslation("Portal");
|
const { t: translate } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setWebauthnSupported(isWebauthnSupported());
|
setWebauthnSupported(isWebauthnSupported());
|
||||||
|
|
|
@ -22,7 +22,7 @@ const SignOut = function (props: Props) {
|
||||||
const redirector = useRedirector();
|
const redirector = useRedirector();
|
||||||
const [timedOut, setTimedOut] = useState(false);
|
const [timedOut, setTimedOut] = useState(false);
|
||||||
const [safeRedirect, setSafeRedirect] = useState(false);
|
const [safeRedirect, setSafeRedirect] = useState(false);
|
||||||
const { t: translate } = useTranslation("Portal");
|
const { t: translate } = useTranslation();
|
||||||
|
|
||||||
const doSignOut = useCallback(async () => {
|
const doSignOut = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -16,7 +16,7 @@ const ResetPasswordStep1 = function () {
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const { createInfoNotification, createErrorNotification } = useNotifications();
|
const { createInfoNotification, createErrorNotification } = useNotifications();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t: translate } = useTranslation("Portal");
|
const { t: translate } = useTranslation();
|
||||||
|
|
||||||
const doInitiateResetPasswordProcess = async () => {
|
const doInitiateResetPasswordProcess = async () => {
|
||||||
if (username === "") {
|
if (username === "") {
|
||||||
|
|
|
@ -25,7 +25,7 @@ const ResetPasswordStep2 = function () {
|
||||||
const [errorPassword1, setErrorPassword1] = useState(false);
|
const [errorPassword1, setErrorPassword1] = useState(false);
|
||||||
const [errorPassword2, setErrorPassword2] = useState(false);
|
const [errorPassword2, setErrorPassword2] = useState(false);
|
||||||
const { createSuccessNotification, createErrorNotification } = useNotifications();
|
const { createSuccessNotification, createErrorNotification } = useNotifications();
|
||||||
const { t: translate } = useTranslation("Portal");
|
const { t: translate } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue