Skip to content

Commit

Permalink
fix(server): i18n etags missing (authelia#3973)
Browse files Browse the repository at this point in the history
This fixes missing etags from locales assets.
  • Loading branch information
james-d-elliott authored Sep 16, 2022
1 parent ec88b67 commit 15110b7
Show file tree
Hide file tree
Showing 15 changed files with 115 additions and 135 deletions.
10 changes: 0 additions & 10 deletions crowdin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,4 @@ files:
- source: /internal/server/locales/en/*
translation: /internal/server/locales/%locale%/%original_file_name%
skip_untranslated_files: true
languages_mapping:
locale:
"de-DE": de
"en-EN": en
"es-ES": es
"fr-FR": fr
"nl-NL": nl
"pt-PT": pt
"ru-RU": ru
"zh-CH": zh
...
18 changes: 10 additions & 8 deletions internal/middlewares/asset_override.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@ import (

// AssetOverride allows overriding and serving of specific embedded assets from disk.
func AssetOverride(root string, strip int, next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
if root == "" {
next(ctx)
if root == "" {
return next
}

return
}
handler := fasthttp.FSHandler(root, strip)
stripper := fasthttp.NewPathSlashesStripper(strip)

return func(ctx *fasthttp.RequestCtx) {
asset := filepath.Join(root, string(stripper(ctx)))

_, err := os.Stat(filepath.Join(root, string(fasthttp.NewPathSlashesStripper(strip)(ctx))))
if err != nil {
if _, err := os.Stat(asset); err != nil {
next(ctx)

return
}

fasthttp.FSHandler(root, strip)(ctx)
handler(ctx)
}
}
97 changes: 75 additions & 22 deletions internal/server/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,21 @@ import (
"github.com/authelia/authelia/v4/internal/utils"
)

//go:embed locales
var locales embed.FS
var (
//go:embed public_html
assets embed.FS

//go:embed public_html
var assets embed.FS
//go:embed locales
locales embed.FS
)

func newPublicHTMLEmbeddedHandler() fasthttp.RequestHandler {
etags := map[string][]byte{}

getEmbedETags(assets, "public_html", etags)
getEmbedETags(assets, assetsRoot, etags)

return func(ctx *fasthttp.RequestCtx) {
p := path.Join("public_html", string(ctx.Path()))
p := path.Join(assetsRoot, string(ctx.Path()))

if etag, ok := etags[p]; ok {
ctx.Response.Header.SetBytesKV(headerETag, etag)
Expand Down Expand Up @@ -66,8 +68,10 @@ func newPublicHTMLEmbeddedHandler() fasthttp.RequestHandler {
}
}

func newLocalesEmbeddedHandler() (handler fasthttp.RequestHandler) {
var languages []string
func newLocalesPathResolver() func(ctx *fasthttp.RequestCtx) (supported bool, asset string) {
var (
languages, dirs []string
)

entries, err := locales.ReadDir("locales")
if err == nil {
Expand All @@ -84,6 +88,10 @@ func newLocalesEmbeddedHandler() (handler fasthttp.RequestHandler) {
lng = strings.SplitN(entry.Name(), "-", 2)[0]
}

if !utils.IsStringInSlice(entry.Name(), dirs) {
dirs = append(dirs, entry.Name())
}

if utils.IsStringInSlice(lng, languages) {
continue
}
Expand All @@ -93,34 +101,79 @@ func newLocalesEmbeddedHandler() (handler fasthttp.RequestHandler) {
}
}

return func(ctx *fasthttp.RequestCtx) {
var (
language, variant, locale, namespace string
)
aliases := map[string]string{
"sv": "sv-SE",
"zh": "zh-CN",
}

language = ctx.UserValue("language").(string)
namespace = ctx.UserValue("namespace").(string)
locale = language
return func(ctx *fasthttp.RequestCtx) (supported bool, asset string) {
var language, namespace, variant, locale string

language, namespace = ctx.UserValue("language").(string), ctx.UserValue("namespace").(string)

if !utils.IsStringInSlice(language, languages) {
return false, ""
}

if v := ctx.UserValue("variant"); v != nil {
variant = v.(string)
locale = fmt.Sprintf("%s-%s", language, variant)
} else {
locale = language
}

var data []byte
ll := language + "-" + strings.ToUpper(language)
alias, ok := aliases[locale]

switch {
case ok:
return true, fmt.Sprintf("locales/%s/%s.json", alias, namespace)
case utils.IsStringInSlice(locale, dirs):
return true, fmt.Sprintf("locales/%s/%s.json", locale, namespace)
case utils.IsStringInSlice(ll, dirs):
return true, fmt.Sprintf("locales/%s-%s/%s.json", language, strings.ToUpper(language), namespace)
default:
return true, fmt.Sprintf("locales/%s/%s.json", locale, namespace)
}
}
}

if data, err = locales.ReadFile(fmt.Sprintf("locales/%s/%s.json", locale, namespace)); err != nil {
if utils.IsStringInSliceFold(language, languages) {
data = []byte("{}")
}
func newLocalesEmbeddedHandler() (handler fasthttp.RequestHandler) {
etags := map[string][]byte{}

if len(data) == 0 {
hfsHandleErr(ctx, err)
getEmbedETags(locales, "locales", etags)

getAssetName := newLocalesPathResolver()

return func(ctx *fasthttp.RequestCtx) {
supported, asset := getAssetName(ctx)

if !supported {
handlers.SetStatusCodeResponse(ctx, fasthttp.StatusNotFound)

return
}

if etag, ok := etags[asset]; ok {
ctx.Response.Header.SetBytesKV(headerETag, etag)
ctx.Response.Header.SetBytesKV(headerCacheControl, headerValueCacheControlETaggedAssets)

if bytes.Equal(etag, ctx.Request.Header.PeekBytes(headerIfNoneMatch)) {
ctx.SetStatusCode(fasthttp.StatusNotModified)

return
}
}

var (
data []byte
err error
)

if data, err = locales.ReadFile(asset); err != nil {
data = []byte("{}")
}

middlewares.SetContentTypeApplicationJSON(ctx)

ctx.SetBody(data)
Expand Down
17 changes: 9 additions & 8 deletions internal/server/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import (
)

const (
embeddedAssets = "public_html/"
swaggerAssets = embeddedAssets + "api/"
apiFile = "openapi.yml"
indexFile = "index.html"
logoFile = "logo.png"
assetsRoot = "public_html"
assetsSwagger = assetsRoot + "/api"

fileOpenAPI = "openapi.yml"
fileIndexHTML = "index.html"
fileLogo = "logo.png"
)

var (
rootFiles = []string{"manifest.json", "robots.txt"}
swaggerFiles = []string{
filesRoot = []string{"manifest.json", "robots.txt"}
filesSwagger = []string{
"favicon-16x16.png",
"favicon-32x32.png",
"index.css",
Expand All @@ -35,7 +36,7 @@ var (
}

// Directories excluded from the not found handler proceeding to the next() handler.
httpServerDirs = []struct {
dirsHTTPServer = []struct {
name, prefix string
}{
{name: "/api", prefix: "/api/"},
Expand Down
18 changes: 9 additions & 9 deletions internal/server/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ func handleNotFound(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
path := strings.ToLower(string(ctx.Path()))

for i := 0; i < len(httpServerDirs); i++ {
if path == httpServerDirs[i].name || strings.HasPrefix(path, httpServerDirs[i].prefix) {
for i := 0; i < len(dirsHTTPServer); i++ {
if path == dirsHTTPServer[i].name || strings.HasPrefix(path, dirsHTTPServer[i].prefix) {
handlers.SetStatusCodeResponse(ctx, fasthttp.StatusNotFound)

return
Expand All @@ -104,9 +104,9 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)

https := config.Server.TLS.Key != "" && config.Server.TLS.Certificate != ""

serveIndexHandler := ServeTemplatedFile(embeddedAssets, indexFile, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https)
serveSwaggerHandler := ServeTemplatedFile(swaggerAssets, indexFile, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https)
serveSwaggerAPIHandler := ServeTemplatedFile(swaggerAssets, apiFile, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https)
serveIndexHandler := ServeTemplatedFile(assetsRoot, fileIndexHTML, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https)
serveSwaggerHandler := ServeTemplatedFile(assetsSwagger, fileIndexHTML, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https)
serveSwaggerAPIHandler := ServeTemplatedFile(assetsSwagger, fileOpenAPI, config.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, config.Session.Name, config.Theme, https)

handlerPublicHTML := newPublicHTMLEmbeddedHandler()
handlerLocales := newLocalesEmbeddedHandler()
Expand All @@ -124,7 +124,7 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
// Static Assets.
r.GET("/", middleware(serveIndexHandler))

for _, f := range rootFiles {
for _, f := range filesRoot {
r.GET("/"+f, handlerPublicHTML)
}

Expand All @@ -139,10 +139,10 @@ func handleRouter(config schema.Configuration, providers middlewares.Providers)
// Swagger.
r.GET("/api/", middleware(serveSwaggerHandler))
r.OPTIONS("/api/", policyCORSPublicGET.HandleOPTIONS)
r.GET("/api/"+apiFile, policyCORSPublicGET.Middleware(middleware(serveSwaggerAPIHandler)))
r.OPTIONS("/api/"+apiFile, policyCORSPublicGET.HandleOPTIONS)
r.GET("/api/"+fileOpenAPI, policyCORSPublicGET.Middleware(middleware(serveSwaggerAPIHandler)))
r.OPTIONS("/api/"+fileOpenAPI, policyCORSPublicGET.HandleOPTIONS)

for _, file := range swaggerFiles {
for _, file := range filesSwagger {
r.GET("/api/"+file, handlerPublicHTML)
}

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
69 changes: 0 additions & 69 deletions internal/server/locales/sv/portal.json

This file was deleted.

File renamed without changes.
17 changes: 10 additions & 7 deletions internal/server/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"text/template"

"github.com/valyala/fasthttp"

"github.com/authelia/authelia/v4/internal/logging"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/utils"
Expand All @@ -19,7 +22,7 @@ import (
func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberMe, resetPassword, resetPasswordCustomURL, session, theme string, https bool) middlewares.RequestHandler {
logger := logging.Logger()

a, err := assets.Open(publicDir + file)
a, err := assets.Open(path.Join(publicDir, file))
if err != nil {
logger.Fatalf("Unable to open %s: %s", file, err)
}
Expand All @@ -43,7 +46,7 @@ func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberM
logoOverride := f

if assetPath != "" {
if _, err := os.Stat(filepath.Join(assetPath, logoFile)); err == nil {
if _, err := os.Stat(filepath.Join(assetPath, fileLogo)); err == nil {
logoOverride = t
}
}
Expand Down Expand Up @@ -71,14 +74,14 @@ func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberM
}

switch {
case publicDir == swaggerAssets:
ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf("base-uri 'self'; default-src 'self'; img-src 'self' https://validator.swagger.io data:; object-src 'none'; script-src 'self' 'unsafe-inline' 'nonce-%s'; style-src 'self' 'nonce-%s'", nonce, nonce))
case publicDir == assetsSwagger:
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf("base-uri 'self'; default-src 'self'; img-src 'self' https://validator.swagger.io data:; object-src 'none'; script-src 'self' 'unsafe-inline' 'nonce-%s'; style-src 'self' 'nonce-%s'", nonce, nonce))
case ctx.Configuration.Server.Headers.CSPTemplate != "":
ctx.Response.Header.Add("Content-Security-Policy", strings.ReplaceAll(ctx.Configuration.Server.Headers.CSPTemplate, cspNoncePlaceholder, nonce))
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, strings.ReplaceAll(ctx.Configuration.Server.Headers.CSPTemplate, cspNoncePlaceholder, nonce))
case os.Getenv("ENVIRONMENT") == dev:
ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf(cspDefaultTemplate, " 'unsafe-eval'", nonce))
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(cspDefaultTemplate, " 'unsafe-eval'", nonce))
default:
ctx.Response.Header.Add("Content-Security-Policy", fmt.Sprintf(cspDefaultTemplate, "", nonce))
ctx.Response.Header.Add(fasthttp.HeaderContentSecurityPolicy, fmt.Sprintf(cspDefaultTemplate, "", nonce))
}

err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, BaseURL, CSPNonce, DuoSelfEnrollment, LogoOverride, RememberMe, ResetPassword, ResetPasswordCustomURL, Session, Theme string }{Base: base, BaseURL: baseURL, CSPNonce: nonce, DuoSelfEnrollment: duoSelfEnrollment, LogoOverride: logoOverride, RememberMe: rememberMe, ResetPassword: resetPassword, ResetPasswordCustomURL: resetPasswordCustomURL, Session: session, Theme: theme})
Expand Down
Loading

0 comments on commit 15110b7

Please sign in to comment.