diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md deleted file mode 100644 index 2a133a3..0000000 --- a/CONTRIBUTORS.md +++ /dev/null @@ -1,59 +0,0 @@ -SSOwat contributors -=================== - -YunoHost is built and maintained by the YunoHost project community. -Everyone is encouraged to submit issues and changes, and to contribute in other ways -- see https://yunohost.org/contribute to find out how. - --- - -SSOwat was initially built by Kload, for YunoHost v2. - -Design was created by Théodore 'Tozz' Faure and Thomas 'Courgette' Lebeau and implemented by Courgette himself. - -Most of code was written by Kload and opi, with help of numerous contributors. - -Translation is made by a bunch of lovely people over the world. - -We would like to thank anyone who ever helped the YunoHost project, and especially the SSOwat project <3 - - -SSOwat Contributors -------------------- - -- Kload -- opi -- Jérôme Lebleu -- Maniack Crudelis -- Julien 'ju' Malik -- M5oul -- Alexander Chalikiopoulos -- Adrien 'Beudbeud' Beudin -- Hnk Reno -- Laurent 'Bram' Peuch -- Loïc 'dzamlo' Damien -- sidddy - - -SSOwat Translators ------------------- - -### French - -- Jean-Baptiste Holcroft - -### German - -- Felix Bartels - -### Hindi - -- Anmol - -### Portuguese - -- Deleted User -- Trollken - -### Spanish - -- Juanu diff --git a/README.md b/README.md index 21c3647..9db97de 100644 --- a/README.md +++ b/README.md @@ -7,29 +7,8 @@ A simple LDAP SSO for NGINX, written in Lua. Translation status -Issues ------- - - [Please report issues to the YunoHost bugtracker](https://github.com/YunoHost/issues). -Requirements ------------- - -- `nginx-extras` from Debian wheezy-backports -- `lua-json` -- `lua-ldap` -- `lua-filesystem` -- `lua-socket` -- `lua-rex-pcre` - -**OR** - -- "OpenResty" flavored NGINX: https://openresty.org/ -- `lua-ldap` -- `lua-filesystem` -- `lua-socket` -- `lua-rex-pcre` - Installation ------------ @@ -74,117 +53,15 @@ If you use YunoHost, you may want to edit the `/etc/ssowat/conf.json.persistent` Only the `portal_domain` SSOwat configuration parameters is required, but it is recommended to know the others to fully understand what you can do with it. ---------------- +- `cookie_secret_file`: Where the secret used for signing and encrypting cookie is stored. It should only be readable by root. +- `cookie_name`: The name of the cookie used for authentication. Its content is expected to be a JWT signed with the cookie secret and should contain a key `user` and `password` (which is needed for Basic HTTP Auth). Because JWT is only encoded and signed (not encrypted), the `password` is expected to be encrypted using the cookie secret. +- `portal_domain`: Domain of the authentication portal. It has to be a domain, IP addresses will not work with SSOwat (**Required**). +- `portal_path`: URI of the authentication portal (**default**: `/ssowat/`). This path **must** end with “`/`”. +- `domains`: List of handled domains (**default**: similar to `portal_domain`). +- `redirected_urls`: Array of URLs and/or URIs to redirect and their redirect URI/URL (**example**: `{ "/": "example.org/subpath" }`). +- `redirected_regex`: Array of regular expressions to be matched against URLs **and** URIs and their redirect URI/URL (**example**: `{ "example.org/megusta$": "example.org/subpath" }`). -### portal_domain - -Domain of the authentication portal. It has to be a domain, IP addresses will not work with SSOwat (**Required**). - ---------------- - -### portal_path - -URI of the authentication portal (**default**: `/ssowat/`). This path **must** end with “`/`”. - ---------------- - -### portal_port - -Web port of the authentication portal (**default**: `443` for `https`, `80` for `http`). - ---------------- - -### portal_scheme - -Whether authentication should use secure connection or not (**default**: `https`). - ---------------- - -### domains - -List of handled domains (**default**: similar to `portal_domain`). - ---------------- - -### ldap_host - -LDAP server hostname (**default**: `localhost`). - ---------------- - -### ldap_group - -LDAP group to search in (**default**: `ou=users,dc=yunohost,dc=org`). - ---------------- - -### ldap_identifier - -LDAP user identifier (**default**: `uid`). - ---------------- - -### ldap_attributes - -User's attributes to fetch from LDAP (**default**: `["uid", "givenname", "sn", "cn", "homedirectory", "mail", "maildrop"]`). - ---------------- - -### ldap_enforce_crypt - -Let SSOwat re-encrypt weakly-encrypted LDAP passwords into the safer sha-512 (crypt) (**default**: `true`). - ---------------- - -### allow_mail_authentication - -Whether users can authenticate with their mail address (**default**: `true`). - ---------------- - -### login_arg - -URI argument to use for cross-domain authentication (**default**: `sso_login`). - ---------------- - -### additional_headers - -Array of additionnal HTTP headers to set once user is authenticated (**default**: `{ "Remote-User": "uid" }`). - ---------------- - -### session_timeout - -The session expiracy time limit in seconds, since the last connection (**default**: `86400` / one day). - ---------------- - -### session_max_timeout - -The session expiracy time limit in seconds (**default**: `604800` / one week). - ---------------- - -### redirected_urls - -Array of URLs and/or URIs to redirect and their redirect URI/URL (**example**: `{ "/": "example.org/subpath" }`). - ---------------- - -### redirected_regex - -Array of regular expressions to be matched against URLs **and** URIs and their redirect URI/URL (**example**: `{ "example.org/megusta$": "example.org/subpath" }`). - ---------------- - -### default_language - -Language code used by default in views (**default**: `en`). - ---------------- - -### permissions +### `permissions` The list of permissions depicted as follows: diff --git a/access.lua b/access.lua index ea1021c..229403a 100644 --- a/access.lua +++ b/access.lua @@ -2,273 +2,155 @@ -- access.lua -- -- This file is executed at every request on a protected domain or server. --- You just have to read this file normally to understand how and when the --- request is handled: redirected, forbidden, bypassed or served. -- --- Get the `cache` persistent shared table -local cache = ngx.shared.cache - --- Generate a unique token if it has not been generated yet -srvkey = cache:get("srvkey") -if not srvkey then - srvkey = random_string() - cache:add("srvkey", srvkey) -end - --- Import helpers -local hlp = require "helpers" - --- Initialize and get configuration -hlp.refresh_config() -local conf = hlp.get_config() - --- Load logging module -local logger = require("log") - -- Just a note for the client to know that he passed through the SSO ngx.header["X-SSO-WAT"] = "You've just been SSOed" -local is_logged_in = hlp.refresh_logged_in() +-- Misc imports +local jwt = require("vendor.luajwtjitsi.luajwtjitsi") +local cipher = require('openssl.cipher') +local rex = require("rex_pcre2") --- --- 1. LOGIN --- --- example: https://mydomain.org/?sso_login=a6e5320f --- --- If the `sso_login` URI argument is set, try a cross-domain authentication --- with the token passed as argument --- -if ngx.var.host ~= conf["portal_domain"] and ngx.var.request_method == "GET" then - uri_args = ngx.req.get_uri_args() - if uri_args[conf.login_arg] then - cda_key = uri_args[conf.login_arg] +-- ########################################################################### +-- 0. Misc helpers because Lua has no sugar ... +-- ########################################################################### - -- Use the `cache` shared table where a username is associated with - -- a CDA key - user = cache:get("CDA|"..cda_key) - if user then - hlp.set_auth_cookie(user, ngx.var.host) - logger.info("Cross-domain authentication: "..user.." connected on "..ngx.var.host) - cache:delete("CDA|"..cda_key) +-- Get configuration (we do this here, the conf is re-read every time unless +-- the file's timestamp didnt change) +local config = require("config") +local conf = config.get_config() + +-- Cache expensive calculations +local cache = ngx.shared.cache + +-- Hash a string using hmac_sha512, return a hexa string +function cached_jwt_verify(data, secret) + res = cache:get(data) + if res == nil then + logger:debug("Result not found in cache, checking login") + -- Perform expensive calculation + decoded, err = jwt.verify(data, "HS256", cookie_secret) + if not decoded then + logger:error(err) + return nil, nil, err end - - uri_args[conf.login_arg] = nil - return hlp.redirect(ngx.var.uri..hlp.uri_args_string(uri_args)) - end -end - - --- --- 2. PORTAL --- --- example: https://mydomain.org/ssowat* --- --- If the URL matches the portal URL, serve a portal file or proceed to a --- portal operation --- -if (ngx.var.host == conf["portal_domain"] or is_logged_in) - and hlp.string.starts(ngx.var.uri, string.sub(conf["portal_path"], 1, -2)) -then - - -- `GET` method will serve a portal file - if ngx.var.request_method == "GET" then - - -- Force portal scheme - if ngx.var.scheme ~= conf["portal_scheme"] then - return hlp.redirect(conf.portal_url) - end - - -- Add a trailing `/` if not present - if ngx.var.uri.."/" == conf["portal_path"] then - return hlp.redirect(conf.portal_url) - end - - -- Get request arguments - uri_args = ngx.req.get_uri_args() - - -- Logout is also called via a `GET` method - -- TODO: change this ? - if uri_args.action and uri_args.action == 'logout' then - logger.debug("Logging out") - return hlp.logout() - - -- If the `r` URI argument is set, it means that we want to - -- be redirected (typically after a login phase) - elseif is_logged_in and uri_args.r then - -- Decode back url - back_url = ngx.decode_base64(uri_args.r) - - -- If `back_url` contains line break, someone is probably trying to - -- pass some additional headers - if string.match(back_url, "(.*)\n") then - hlp.flash("fail", hlp.t("redirection_error_invalid_url")) - logger.error("Redirection url is invalid") - return hlp.redirect(conf.portal_url) - end - - -- Get managed domains - local managed_domain = false - for _, domain in ipairs(conf["domains"]) do - local escaped_domain = domain:gsub("-", "%%-") -- escape dash for pattern matching - if string.match(back_url, "^http[s]?://"..escaped_domain.."/") then - logger.debug("Redirection to a managed domain found") - managed_domain = true - break - end - end - - -- If redirection does not match one of the managed domains - -- redirect to portal home page - if not managed_domain then - hlp.flash("fail", hlp.t("redirection_error_unmanaged_domain")) - logger.error("Redirection to an external domain aborted") - return hlp.redirect(conf.portal_url) - end - - - -- In case the `back_url` is not on the same domain than the - -- current one, create a redirection with a CDA key - local ngx_host_escaped = ngx.var.host:gsub("-", "%%-") -- escape dash for pattern matching - if not string.match(back_url, "^http[s]?://"..ngx_host_escaped.."/") - and not string.match(back_url, ".*"..conf.login_arg.."=%d+$") then - local cda_key = hlp.set_cda_key() - if string.match(back_url, ".*?.*") then - back_url = back_url.."&" - else - back_url = back_url.."?" - end - back_url = back_url.."sso_login="..cda_key - end - - return hlp.redirect(back_url) - - - -- In case we want to serve portal login or assets for portal, just - -- serve it - elseif is_logged_in - or ngx.var.uri == conf["portal_path"] - or (hlp.string.starts(ngx.var.uri, conf["portal_path"].."assets") - and (not ngx.var.http_referer - or hlp.string.starts(ngx.var.http_referer, conf.portal_url))) - then - -- If this is an asset, enable caching - if hlp.string.starts(ngx.var.uri, conf["portal_path"].."assets") - then - return hlp.serve(ngx.var.uri, "static_asset") - else - return hlp.serve(ngx.var.uri) - end - - - -- If all the previous cases have failed, redirect to portal - else - hlp.flash("info", hlp.t("please_login")) - logger.debug("User should log in to be able to access "..ngx.var.uri) - -- Force the scheme to HTTPS. This is to avoid an issue with redirection loop - -- when trying to access http://main.domain.tld/ (SSOwat finds that user aint - -- logged in, therefore redirects to SSO, which redirects to the back_url, which - -- redirect to SSO, ..) - local back_url = "https://" .. ngx.var.host .. ngx.var.uri .. hlp.uri_args_string() - return hlp.redirect(conf.portal_url.."?r="..ngx.encode_base64(back_url)) - end - - - -- `POST` method is basically use to achieve editing operations - elseif ngx.var.request_method == "POST" then - - -- CSRF protection, only proceed if we are editing from the same - -- domain - if hlp.string.starts(ngx.var.http_referer, conf.portal_url) then - if hlp.string.ends(ngx.var.uri, conf["portal_path"].."password.html") - or hlp.string.ends(ngx.var.uri, conf["portal_path"].."edit.html") - then - logger.debug("User attempts to edit its information") - return hlp.edit_user() - else - logger.debug("User attempts to log in") - return hlp.login() - end - else - -- Redirect to portal - hlp.flash("fail", hlp.t("please_login_from_portal")) - logger.debug("Invalid POST request not coming from the portal url...") - return hlp.redirect(conf.portal_url) - end - end -end - --- --- 2 ... continued : portal assets that are available on every domains --- --- For example: `https://whatever.org/ynhpanel.js` will serve the --- `/yunohost/sso/assets/js/ynhpanel.js` file. --- - -if is_logged_in then - assets = { - ["/ynh_portal.js"] = "js/ynh_portal.js", - ["/ynh_userinfo.json"] = "ynh_userinfo.json", - ["/ynh_overlay.css"] = "css/ynh_overlay.css" - } - theme_dir = "/usr/share/ssowat/portal/assets/themes/"..conf.theme - local pfile = io.popen('find "'..theme_dir..'" -not -path "*/\\.*" -type f -exec realpath --relative-to "'..theme_dir..'" {} \\;') - for filename in pfile:lines() do - assets["/ynhtheme/"..filename] = "themes/"..conf.theme.."/"..filename - end - pfile:close() - - for shortcut, full in pairs(assets) do - if ngx.var.uri == shortcut then - logger.debug("Serving static asset "..full) - return hlp.serve("/yunohost/sso/assets/"..full, "static_asset") - end - end -end - - --- --- 3. REDIRECTED URLS --- --- If the URL matches one of the `redirected_urls` in the configuration file, --- just redirect to the target URL/URI --- - -function detect_redirection(redirect_url) - if hlp.string.starts(redirect_url, "http://") - or hlp.string.starts(redirect_url, "https://") then - return hlp.redirect(redirect_url) - elseif hlp.string.starts(redirect_url, "/") then - return hlp.redirect(ngx.var.scheme.."://"..ngx.var.host..redirect_url) + -- As explained in set_basic_auth_header(), user and hashed password do not contain ':' + -- And cache cannot contain tables, so we use "user:password" format + cached = decoded["user"]..":"..decoded["pwd"] + cache:set(data, cached, 120) + logger:debug("Result saved in cache") + return decoded["user"], decoded["pwd"], err else - return hlp.redirect(ngx.var.scheme.."://"..redirect_url) + logger:debug("Result found in cache") + user, pwd = res:match("([^:]+):(.*)") + return user, pwd, nil + end +end + +-- The 'match' function uses PCRE regex as default +-- If '%.' is found in the regex, we assume it's a LUA regex (legacy code) +-- 'match' returns the matched text. +function match(s, regex) + if not string.find(regex, '%%%.') then + return rex.match(s, regex) + else + return string.match(s,regex) + end +end + +-- Test whether a string starts with another +function string.starts(String, Start) + if not String then + return false + end + return string.sub(String, 1, string.len(Start)) == Start +end + +-- Convert a table of arguments to an URI string +function uri_args_string(args) + if not args then + args = ngx.req.get_uri_args() + end + String = "?" + for k,v in pairs(args) do + String = String..tostring(k).."="..tostring(v).."&" + end + return string.sub(String, 1, string.len(String) - 1) +end + +-- ########################################################################### +-- 1. AUTHENTICATION +-- Check wether or not this is a logged-in user +-- This is not run immediately but only if: +-- - the app is not public +-- - and/or auth_headers is enabled for this app +-- ########################################################################### + +function check_authentication() + + -- cf. src/authenticators/ldap_ynhuser.py in YunoHost to see how the cookie is actually created + + local cookie = ngx.var["cookie_" .. conf["cookie_name"]] + if cookie == nil then + return false, nil, nil + end + + user, pwd, err = cached_jwt_verify(cookie, cookie_secret) + + -- FIXME : maybe also check that the cookie was delivered for the requested domain (or a parent?) + + -- FIXME : we might want also a way to identify expired/invalidated cookies, + -- e.g. a user that got deleted after being logged in, or a user that logged out ... + + if err ~= nil then + return false, nil, nil + else + return true, user, pwd + end +end + +-- ########################################################################### +-- 2. REDIRECTED URLS +-- If the URL matches one of the `redirected_urls` in the configuration file, +-- just redirect to the target URL/URI +-- ########################################################################### + +function convert_to_absolute_url(redirect_url) + if string.starts(redirect_url, "http://") + or string.starts(redirect_url, "https://") then + return redirect_url + elseif string.starts(redirect_url, "/") then + return ngx.var.scheme.."://"..ngx.var.host..redirect_url + else + return ngx.var.scheme.."://"..redirect_url end end if conf["redirected_urls"] then for url, redirect_url in pairs(conf["redirected_urls"]) do - if url == ngx.var.host..ngx.var.uri..hlp.uri_args_string() - or url == ngx.var.scheme.."://"..ngx.var.host..ngx.var.uri..hlp.uri_args_string() - or url == ngx.var.uri..hlp.uri_args_string() then - logger.debug("Requested URI is in redirected_urls") - detect_redirection(redirect_url) + if url == ngx.var.host..ngx.var.uri..uri_args_string() + or url == ngx.var.scheme.."://"..ngx.var.host..ngx.var.uri..uri_args_string() + or url == ngx.var.uri..uri_args_string() then + logger:debug("Found in redirected_urls, redirecting to "..url) + ngx.redirect(convert_to_absolute_url(redirect_url)) end end end if conf["redirected_regex"] then for regex, redirect_url in pairs(conf["redirected_regex"]) do - if hlp.match(ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex) - or hlp.match(ngx.var.scheme.."://"..ngx.var.host..ngx.var.uri..hlp.uri_args_string(), regex) - or hlp.match(ngx.var.uri..hlp.uri_args_string(), regex) then - logger.debug("Requested URI is in redirected_regex") - detect_redirection(redirect_url) + if match(ngx.var.host..ngx.var.uri..uri_args_string(), regex) + or match(ngx.var.scheme.."://"..ngx.var.host..ngx.var.uri..uri_args_string(), regex) + or match(ngx.var.uri..uri_args_string(), regex) then + logger:debug("Found in redirected_regex, redirecting to "..url) + ngx.redirect(convert_to_absolute_url(redirect_url)) end end end --- --- 4. IDENTIFY THE RELEVANT PERMISSION +-- ########################################################################### +-- 3. IDENTIFY PERMISSION MATCHING THE REQUESTED URL -- -- In particular, the conf is filled with permissions such as: -- @@ -287,7 +169,7 @@ end -- -- And we find the best matching permission by trying to match the request uri -- against all the uris rules/regexes from the conf and keep the longest matching one. --- +-- ########################################################################### permission = nil longest_url_match = "" @@ -305,7 +187,7 @@ for permission_name, permission_infos in pairs(conf["permissions"]) do url = "^"..url end - local m = hlp.match(ngx_full_url, url) + local m = match(ngx_full_url, url) if m ~= nil and string.len(m) > string.len(longest_url_match) then longest_url_match = m permission = permission_infos @@ -315,65 +197,168 @@ for permission_name, permission_infos in pairs(conf["permissions"]) do end end +-- ########################################################################### +-- 4. CHECK USER HAS ACCESS +-- Either because the permission is set as "public: true", +-- Or because the logged-in user is listed in the "users" list of the perm +-- ########################################################################### ---- ---- 5. CHECK CLIENT-PROVIDED AUTH HEADER (should almost never happen?) ---- +function element_is_in_table(element, table) + if table then + for _, el in pairs(table) do + if el == element then + return true + end + end + end -if permission ~= nil then + return false +end + +-- Check whether the app is public access +function check_public_access(permission) + if permission == nil then + logger:debug("No permission matching request for "..ngx.var.uri.." ... Assuming access is denied") + return false + end + + if permission["public"] then + logger:debug("Someone tries to access "..ngx.var.uri.." (corresponding perm: "..permission["id"]..")") + return true + end +end + +-- Check whether a user is allowed to access a URL using the `permissions` directive +-- of the configuration file +function check_has_access(permission) + + -- Public access + if authUser == nil or permission["public"] then + user = authUser or "A visitor" + logger:debug(user.." tries to access "..ngx.var.uri.." (corresponding perm: "..permission["id"]..")") + return permission["public"] + end + + logger:debug("User "..authUser.." tries to access "..ngx.var.uri.." (corresponding perm: "..permission["id"]..")") + + -- The user has permission to access the content if he is in the list of allowed users + if element_is_in_table(authUser, permission["users"]) then + logger:debug("User "..authUser.." can access "..ngx.var.host..ngx.var.uri..uri_args_string()) + return true + else + logger:debug("User "..authUser.." cannot access "..ngx.var.uri) + return false + end +end + +if check_public_access(permission) then + has_access = true +else + is_logged_in, authUser, authPasswordEnc = check_authentication() + has_access = check_has_access(permission) +end + +-- ########################################################################### +-- 5. CLEAR USER-PROVIDED AUTH HEADER +-- +-- Which could be spoofing attempts +-- Unfortunately we can't yolo-clear them on every route because some +-- apps use legit basic auth mechanism ... +-- +-- "Remote user" refers to the fact that Basic Auth headers is coupled to +-- the $remote_user var in nginx, typically used by PHP apps +-- ########################################################################### + +if permission ~= nil and ngx.req.get_headers()["Authorization"] ~= nil then perm_user_remote_user_var_in_nginx_conf = permission["use_remote_user_var_in_nginx_conf"] if perm_user_remote_user_var_in_nginx_conf == nil or perm_user_remote_user_var_in_nginx_conf == true then - is_logged_in_with_basic_auth = hlp.validate_or_clear_basic_auth_header_provided_by_client() - - -- NB: is_logged_in_with_basic_auth can be false, true or nil - if is_logged_in_with_basic_auth == false then - return ngx.exit(ngx.HTTP_UNAUTHORIZED) - elseif is_logged_in_with_basic_auth == true then - is_logged_in = true + -- Ignore if not a Basic auth header + -- otherwise, we interpret this as a Auth header spoofing attempt and clear it + local auth_header_from_client = ngx.req.get_headers()["Authorization"] + _, _, b64_cred = string.find(auth_header_from_client, "^Basic%s+(.+)$") + if b64_cred ~= nil then + ngx.req.clear_header("Authorization") end end end +-- ########################################################################### +-- 6. EFFECTIVELY PASS OR DENY ACCESS -- +-- If the user has access (either because app is public OR logged in + authorized) +-- -> pass + possibly inject the Basic Auth header on the fly such that the app can know which user is logged in -- --- 6. APPLY PERMISSION --- --- +-- Otherwise, the user can't access +-- -> either because not logged in at all, in that case, redirect to the portal WITH a callback url to redirect to after logging in +-- -> or because user is logged in, but has no access .. in that case just redirect to the portal +-- ########################################################################### + +function set_basic_auth_header() + + -- cf. https://en.wikipedia.org/wiki/Basic_access_authentication + + -- authPasswordEnc is actually a string formatted as | + -- For example: ctl8kk5GevYdaA5VZ2S88Q==|yTAzCx0Gd1+MCit4EQl9lA== + -- The password is encoded using AES-256-CBC with the IV being the right-side data + -- cf. src/authenticators/ldap_ynhuser.py in YunoHost to see how the cookie is actually created + local password_enc_b64, iv_b64 = authPasswordEnc:match("([^|]+)|([^|]+)") + local password_enc = ngx.decode_base64(password_enc_b64) + local iv = ngx.decode_base64(iv_b64) + local password = cipher.new('aes-256-cbc'):decrypt(cookie_secret, iv):final(password_enc) + + -- Set `Authorization` header to enable HTTP authentification + ngx.req.set_header("Authorization", "Basic "..ngx.encode_base64( + authUser..":"..password + )) +end -- 1st case : client has access - -if hlp.has_access(permission) then - - if is_logged_in then - -- If the user is logged in, refresh_cache - hlp.refresh_user_cache() - - -- If Basic Authorization header are enable for this permission, - -- add it to the response - if permission["auth_header"] then - hlp.set_headers() - else - hlp.clear_headers() +if has_access then + -- If Basic Authorization header are enable for this permission, + -- check if the user is actually logged in... + if permission["auth_header"] then + if is_logged_in == nil then + -- Login check was not performed yet because the app is public + logger:debug("Checking authentication because the app requires auth_header") + is_logged_in, authUser, authPasswordEnc = check_authentication() + end + if is_logged_in then + -- add it to the response + set_basic_auth_header() end - else - hlp.clear_headers() end - return hlp.pass() + -- Pass + logger:debug("Allowing to pass through "..ngx.var.uri) + return -- 2nd case : no access ... redirect to portal / login form else - if is_logged_in then - return hlp.redirect(conf.portal_url) - else - -- Only display this if HTTPS. For HTTP, we can't know if the user really is - -- logged in or not, because the cookie is available only in HTTP... - if ngx.var.scheme == "https" then - hlp.flash("info", hlp.t("please_login")) - end + portal_domain = conf["domain_portal_urls"][ngx.var.host] + if portal_domain == nil then + logger:debug("Domain " .. ngx.var.host .. " is not configured for SSOWat") + ngx.status = 400 + ngx.header.content_type = "plain/text" + ngx.say("Unmanaged domain: " .. ngx.var.host) + return + end + portal_url = "https://" .. portal_domain + logger:debug("Redirecting to portal : " .. portal_url) - local back_url = "https://" .. ngx.var.host .. ngx.var.uri .. hlp.uri_args_string() - return hlp.redirect(conf.portal_url.."?r="..ngx.encode_base64(back_url)) + if is_logged_in then + return ngx.redirect(portal_url) + else + local back_url = "https://" .. ngx.var.host .. ngx.var.uri .. uri_args_string() + + -- User ain't logged in, redirect to the portal where we expect the user to login, + -- then be redirected to the original URL by the portal, encoded as base64 + -- + -- NB. for security reason, the client/app handling the callback should check + -- that the back URL is legit, i.e it should be on the same domain (or a subdomain) + -- than the portal. Otherwise, a malicious actor could create a deceptive link + -- that would in fact redirect to a different domain, tricking the user that may + -- not realize this. + return ngx.redirect(portal_url.."?r="..ngx.encode_base64(back_url)) end end diff --git a/conf.json.example b/conf.json.example index aceaea9..fa5ab05 100644 --- a/conf.json.example +++ b/conf.json.example @@ -1,13 +1,8 @@ { - "additional_headers": { - "Auth-User": "uid", - "Email": "mail", - "Name": "cn", - "Remote-User": "uid" - }, - "domains": [ - "example.tld", - "example.org" + "domain_portal_urls": [ + "example.tld": "example.tld/yunohost/sso", + "sub.example.tld": "example.tld/yunohost/sso", + "foobar.org": "foobar.org/yunohost/sso" ], "permissions": { "core_skipped": { @@ -60,10 +55,8 @@ ] } }, - "portal_domain": "example.tld", - "portal_path": "/yunohost/sso/", "redirected_regex": { "example.tld/yunohost[\\/]?$": "https://example.tld/yunohost/sso/" }, "redirected_urls": {} -} \ No newline at end of file +} diff --git a/config.lua b/config.lua index f574af0..a7ac64f 100644 --- a/config.lua +++ b/config.lua @@ -6,11 +6,31 @@ module('config', package.seeall) +local lfs = require("lfs") +local json = require("json") + local config_attributes = nil local config_persistent_attributes = nil local conf = {} +local conf_path = "/etc/ssowat/conf.json" + + +function get_cookie_secret() + + local conf_file = assert(io.open(conf_path, "r"), "Configuration file is missing") + local conf_ = json.decode(conf_file:read("*all")) + conf_file:close() + + local cookie_secret_path = conf_["cookie_secret_file"] or "/etc/yunohost/.ssowat_cookie_secret" + local cookie_secret_file = assert(io.open(cookie_secret_path, "r"), "Cookie secret file is missing") + local cookie_secret = cookie_secret_file:read("*all") + cookie_secret_file:close() + + return cookie_secret +end + function compare_attributes(file_attributes1, file_attributes2) if file_attributes1 == nil and file_attributes2 == nil then return true @@ -20,15 +40,6 @@ function compare_attributes(file_attributes1, file_attributes2) return file_attributes1["modification"] == file_attributes2["modification"] and file_attributes1["size"] == file_attributes2["size"] end -function update_language() - -- Set the prefered language from the `Accept-Language` header - conf.lang = ngx.req.get_headers()["Accept-Language"] - - if conf.lang then - conf.lang = string.sub(conf.lang, 1, 2) - end -end - function get_config() -- Get config files attributes (timestamp modification and size) @@ -36,11 +47,9 @@ function get_config() local new_config_persistent_attributes = lfs.attributes(conf_path..".persistent", {"modification", "size"}) if compare_attributes(new_config_attributes, config_attributes) and compare_attributes(new_config_persistent_attributes, config_persistent_attributes) then - update_language() return conf -- If the file is being written, its size may be 0 and reloading fails, return the last valid config elseif new_config_attributes == nil or new_config_attributes["size"] == 0 then - update_language() return conf end @@ -78,55 +87,10 @@ function get_config() end end - - -- Default configuration values - default_conf = { - portal_scheme = "https", - portal_path = "/ssowat/", - local_portal_domain = "yunohost.local", - domains = { conf["portal_domain"], "yunohost.local" }, - session_timeout = 60 * 60 * 24, -- one day - session_max_timeout = 60 * 60 * 24 * 7, -- one week - login_arg = "sso_login", - ldap_host = "localhost", - ldap_group = "ou=users,dc=yunohost,dc=org", - ldap_identifier = "uid", - ldap_enforce_crypt = true, - skipped_urls = {}, - ldap_attributes = {"uid", "givenname", "sn", "cn", "homedirectory", "mail", "maildrop"}, - allow_mail_authentication = true, - default_language = "en", - theme = "default", - logging = "fatal", -- Only log fatal messages by default (so apriori nothing) - permissions = {} - } - - - -- Load default values unless they are set in the configuration file. - for param, default_value in pairs(default_conf) do - conf[param] = conf[param] or default_value + -- Always skip the portal urls to avoid redirection looping. + for domain, portal_url in pairs(conf["domain_portal_urls"]) do + table.insert(conf["permissions"]["core_skipped"]["uris"], portal_url) end - - - -- If you access the SSO by a local domain, change the portal domain to - -- avoid unwanted redirections. - if ngx.var.host == conf["local_portal_domain"] then - conf["original_portal_domain"] = conf["portal_domain"] - conf["portal_domain"] = conf["local_portal_domain"] - end - - - -- Build portal full URL out of the configuration values - conf.portal_url = conf["portal_scheme"].."://".. - conf["portal_domain"].. - conf["portal_path"] - - - -- Always skip the portal to avoid redirection looping. - table.insert(conf["permissions"]["core_skipped"]["uris"], conf["portal_domain"]..conf["portal_path"]) - - update_language() - return conf end diff --git a/debian/control b/debian/control index ff60631..17a0a0f 100644 --- a/debian/control +++ b/debian/control @@ -7,7 +7,7 @@ Standards-Version: 3.9.1 Package: ssowat Architecture: all -Depends: nginx-extras (>=1.6.2), lua-ldap (>=1.3.1), lua-json, lua-rex-pcre2, lua-filesystem, lua-socket, whois +Depends: nginx-extras (>=1.6.2), lua-json, lua-rex-pcre2, lua-basexx, lua-luaossl, lua-logging, whois Homepage: https://yunohost.org Description: user portal with single sign-on designed for Yunohost A minimalist user portal with single sign-on, designed to be diff --git a/helpers.lua b/helpers.lua deleted file mode 100644 index eea03d8..0000000 --- a/helpers.lua +++ /dev/null @@ -1,1121 +0,0 @@ --- --- helpers.lua --- --- This is a file called at every request by the `access.lua` file. It contains --- a set of useful functions related to HTTP and LDAP. --- - -module('helpers', package.seeall) - -local cache = ngx.shared.cache -local conf = config.get_config() -local logger = require("log") - --- url parser, c.f. https://rosettacode.org/wiki/URL_parser#Lua -local url_parser = require "socket.url" - --- Import Perl regular expressions library -local rex = require "rex_pcre2" - -local is_logged_in = false - -function refresh_config() - conf = config.get_config() -end - -function get_config() - return conf -end - --- The 'match' function uses PCRE regex as default --- If '%.' is found in the regex, we assume it's a LUA regex (legacy code) --- 'match' returns the matched text. -function match(s, regex) - if not string.find(regex, '%%%.') then - return rex.match(s, regex) - else - return string.match(s,regex) - end -end - --- Read a FS stored file -function read_file(file) - local f = io.open(file, "rb") - if not f then return false end - local content = f:read("*all") - f:close() - return content -end - - --- Lua has no sugar :D -function is_in_table(t, v) - for key, value in ipairs(t) do - if value == v then return key end - end -end - - --- Get the index of a value in a table -function index_of(t,val) - for k,v in ipairs(t) do - if v == val then return k end - end -end - - --- Test whether a string starts with another -function string.starts(String, Start) - if not String then - return false - end - return string.sub(String, 1, string.len(Start)) == Start -end - - --- Test whether a string ends with another -function string.ends(String, End) - return End=='' or string.sub(String, -string.len(End)) == End -end - - --- Find a string by its translate key in the right language -function t(key) - if conf.lang and i18n[conf.lang] and i18n[conf.lang][key] then - return i18n[conf.lang][key] - else - return i18n[conf["default_language"]][key] or "" - end -end - - --- Store a message in the flash shared table in order to display it at the --- next response -function flash(wat, message) - if wat == "fail" - or wat == "win" - or wat == "info" - then - flashs[wat] = message - end -end - - --- Hash a string using hmac_sha512, return a hexa string -function hmac_sha512(key, message) - local cache_key = key..":"..message - - if not cache:get(cache_key) then - -- lua ecosystem is a disaster and it was not possible to find a good - -- easily multiplatform integrable code for this - -- - -- this is really dirty and probably leak the key and the message in the process list - -- but if someone got there I guess we really have other problems so this is acceptable - -- and also this is way better than the previous situation - local pipe = io.popen("echo -n '" ..message:gsub("'", "'\\''").. "' | openssl dgst -sha512 -hmac '" ..key:gsub("'", "'\\''").. "'") - - -- openssl returns something like this: - -- root@yunohost:~# echo -n "qsd" | openssl sha512 -hmac "key" - -- SHA2-512(stdin)= f1c2b1658fe64c5a3d16459f2f4eea213e4181905c190235b060ab2a4e7d6a41c15ea2c246828537a1e32ae524b7a7ed309e6d296089194c3e3e3efb98c1fbe3 - -- - -- so we need to remove the "SHA2-512(stdin)= " at the beginning ("(stdin)= " on older openssl version) - local line = pipe:read() - local hash = line:sub(line:find("=") + 2) - pipe:close() - - cache:set(cache_key, hash, conf["session_timeout"]) - return hash - else - return cache:get(cache_key) - end -end - - --- Convert a table of arguments to an URI string -function uri_args_string(args) - if not args then - args = ngx.req.get_uri_args() - end - String = "?" - for k,v in pairs(args) do - String = String..tostring(k).."="..tostring(v).."&" - end - return string.sub(String, 1, string.len(String) - 1) -end - - --- Set the Cross-Domain-Authentication key for a specific user -function set_cda_key() - local cda_key = random_string() - cache:set("CDA|"..cda_key, authUser, 10) - return cda_key -end - - --- Compute and set the authentication cookie --- --- Sets 3 cookies containing: --- * The username --- * The expiration time --- * A hash of those information along with the client IP address and a unique --- session key --- --- It enables the SSO to quickly retrieve the username and the session --- expiration time, and to prove their authenticity to avoid session hijacking. --- -function set_auth_cookie(user, domain) - local maxAge = conf["session_max_timeout"] - local expire = ngx.req.start_time() + maxAge - local session_key = cache:get("session_"..user) - if not session_key then - session_key = random_string() - cache:add("session_"..user, session_key, conf["session_max_timeout"]) - end - local hash = hmac_sha512(srvkey, - user.. - "|"..expire.. - "|"..session_key) - local cookie_str = "; Domain=."..domain.. - "; Path=/".. - "; Expires="..ngx.cookie_time(expire).. - "; Secure".. - "; HttpOnly".. - "; SameSite=Lax" - - ngx.header["Set-Cookie"] = { - "SSOwAuthUser="..user..cookie_str, - "SSOwAuthHash="..hash..cookie_str, - "SSOwAuthExpire="..expire..cookie_str - } - logger.info("Hash "..hash.." generated for "..user.."@"..ngx.var.remote_addr) -end - - --- Expires the 3 session cookies -function delete_cookie() - for _, domain in ipairs(conf["domains"]) do - local cookie_str = "; Domain=."..domain.. - "; Path=/".. - "; Expires="..ngx.cookie_time(0).. - "; Secure".. - "; HttpOnly".. - "; SameSite=Lax" - ngx.header["Set-Cookie"] = { - "SSOwAuthUser="..cookie_str, - "SSOwAuthHash="..cookie_str, - "SSOwAuthExpire="..cookie_str - } - end -end - - --- Validate authentification --- --- Check if the session cookies are set, and rehash server + client information --- to match the session hash. --- -function refresh_logged_in() - local expireTime = ngx.var.cookie_SSOwAuthExpire - local user = ngx.var.cookie_SSOwAuthUser - local authHash = ngx.var.cookie_SSOwAuthHash - - authUser = nil - is_logged_in = false - - if expireTime and expireTime ~= "" - and authHash and authHash ~= "" - and user and user ~= "" - then - -- Check expire time - if (ngx.req.start_time() <= tonumber(expireTime)) then - -- Check hash - local session_key = cache:get("session_"..user) - if session_key and session_key ~= "" then - -- Check cache - if cache:get(user.."-password") then - local hash = hmac_sha512(srvkey, - user.. - "|"..expireTime.. - "|"..session_key) - is_logged_in = hash == authHash - if is_logged_in then - authUser = user - return true - else - failReason = "Hash not matching" - end - else - failReason = "No {user}-password entry in cache" - end - else - failReason = "No session key" - end - else - failReason = "Cookie expired" - end - logger.debug("SSOwat cookies rejected for "..user.."@"..ngx.var.remote_addr.." : "..failReason) - return false - end - - return is_logged_in -end - -function validate_or_clear_basic_auth_header_provided_by_client() - - -- Ignore if no Auth header - local auth_header = ngx.req.get_headers()["Authorization"] - if auth_header == nil then - return nil - end - - -- Ignore if not a Basic auth header - _, _, b64_cred = string.find(auth_header, "^Basic%s+(.+)$") - if b64_cred == nil then - return nil - end - - -- Try to authenticate the user, - -- or remove the Auth header if not valid - _, _, user, password = string.find(ngx.decode_base64(b64_cred), "^([^:]+):(.+)$") - user = authenticate(user, password) - if user then - logger.debug("User got authenticated through basic auth") - authUser = user - return true - else - ngx.req.clear_header("Authorization") - return false -- ngx.exit(ngx.HTTP_UNAUTHORIZED) - end - -end - - - --- Check whether a user is allowed to access a URL using the `permissions` directive --- of the configuration file -function has_access(permission, user) - user = user or authUser - - if permission == nil then - logger.debug("No permission matching request for "..ngx.var.uri) - return false - end - - -- Public access - if user == nil or permission["public"] then - user = user or "A visitor" - logger.debug(user.." tries to access "..ngx.var.uri.." (corresponding perm: "..permission["id"]..")") - return permission["public"] - end - - logger.debug("User "..user.." tries to access "..ngx.var.uri.." (corresponding perm: "..permission["id"]..")") - - -- The user has permission to access the content if he is in the list of allowed users - if element_is_in_table(user, permission["users"]) then - logger.debug("User "..user.." can access "..ngx.var.host..ngx.var.uri..uri_args_string()) - return true - else - logger.debug("User "..user.." cannot access "..ngx.var.uri) - return false - end -end - -function element_is_in_table(element, table) - if table then - for _, el in pairs(table) do - if el == element then - return true - end - end - end - - return false -end - --- Authenticate a user against the LDAP database using a username or an email --- address. --- Reminder: conf["ldap_identifier"] is "uid" by default -function authenticate(user, password) - -- Try to find the username from an email address by openning an anonymous - -- LDAP connection and check if the email address exists - if conf["allow_mail_authentication"] and string.find(user, "@") then - ldap = lualdap.open_simple(conf["ldap_host"]) - for dn, attribs in ldap:search { - base = conf["ldap_group"], - scope = "onelevel", - sizelimit = 1, - filter = "(mail="..user..")", - attrs = {conf["ldap_identifier"]} - } do - if attribs[conf["ldap_identifier"]] then - logger.debug("Use email: "..user) - user = attribs[conf["ldap_identifier"]] - else - logger.error("Unknown email: "..user) - return false - end - end - ldap:close() - end - - -- Now that we have a username, we can try connecting to the LDAP base. - connected = lualdap.open_simple ( - conf["ldap_host"], - conf["ldap_identifier"].."=".. user ..","..conf["ldap_group"], - password - ) - - cache:flush_expired() - - -- If we are connected, we can retrieve the password and put it in the - -- cache shared table in order to eventually reuse it later when updating - -- profile information or just passing credentials to an application. - if connected then - if conf['ldap_enforce_crypt'] then - ensure_user_password_uses_strong_hash(connected, user, password) - end - cache:add(user.."-password", password, conf["session_timeout"]) - ngx.log(ngx.NOTICE, "Connected as: "..user) - logger.info("User "..user.." successfully authenticated from "..ngx.var.remote_addr) - return user - - -- Else, the username/email or the password is wrong - else - -- N.B. : the ngx.log is important and is related to the regex used by - -- the fail2ban rule to detect (and ban) failed login attempts - ngx.log(ngx.ERR, "Connection failed for: "..user) - logger.error("Authentication failure for user "..user.." from "..ngx.var.remote_addr) - return false - end -end - -function delete_user_info_cache(user) - cache:delete(user.."-"..conf["ldap_identifier"]) - local i = 2 - while cache:get(user.."-mail|"..i) do - cache:delete(user.."-mail|"..i) - i = i + 1 - end - local i = 2 - while cache:get(user.."-maildrop|"..i) do - cache:delete(user.."-maildrop|"..i) - i = i + 1 - end -end - --- Set the authentication headers in order to pass credentials to the --- application underneath. -function set_headers(user) - local user = user or authUser - -- Set `Authorization` header to enable HTTP authentification - ngx.req.set_header("Authorization", "Basic "..ngx.encode_base64( - user..":"..cache:get(user.."-password") - )) - - -- Set optionnal additional headers (typically to pass email address) - for k, v in pairs(conf["additional_headers"]) do - ngx.req.set_header(k, cache:get(user.."-"..v)) - end - -end - --- Removes the authentication headers. Call me when: --- - app is public and user is not authenticated --- - app requests that no authentication headers be sent --- Prevents user from pretending to be someone else on public apps -function clear_headers() - -- NB: Basic Auth header is cleared in validate_or_clear_basic_auth_header_provided_by_client - for k, v in pairs(conf["additional_headers"]) do - ngx.req.clear_header(k) - end -end - -function refresh_user_cache(user) - -- We definitely don't want to pass credentials on a non-encrypted - -- connection. - if ngx.var.scheme ~= "https" then - return redirect("https://"..ngx.var.host..ngx.var.uri..uri_args_string()) - end - - local user = user or authUser - - -- If the password is not in cache or if the cache has expired, ask for - -- logging. - if not cache:get(user.."-password") then - flash("info", t("please_login")) - local back_url = ngx.var.scheme .. "://" .. ngx.var.host .. ngx.var.uri .. uri_args_string() - return redirect(conf.portal_url.."?r="..ngx.encode_base64(back_url)) - end - - -- If the user information is not in cache, open an LDAP connection and - -- fetch it. - if not cache:get(user.."-"..conf["ldap_identifier"]) then - ldap = lualdap.open_simple( - conf["ldap_host"], - conf["ldap_identifier"].."=".. user ..","..conf["ldap_group"], - cache:get(user.."-password") - ) - -- If the ldap connection fail (because the password was changed). - -- Logout the user and invalid the password - if not ldap then - logger.debug("LDAP connection failed. Disconnect user : ".. user) - cache:delete(authUser.."-password") - flash("info", t("please_login")) - local back_url = ngx.var.scheme .. "://" .. ngx.var.host .. ngx.var.uri .. uri_args_string() - return redirect(conf.portal_url.."?r="..ngx.encode_base64(back_url)) - end - logger.debug("Reloading LDAP values for: "..user) - for dn, attribs in ldap:search { - base = conf["ldap_identifier"].."=".. user ..","..conf["ldap_group"], - scope = "base", - sizelimit = 1, - attrs = conf["ldap_attributes"] - } do - for k,v in pairs(attribs) do - if type(v) == "table" then - for k2,v2 in ipairs(v) do - if k2 == 1 then cache:set(user.."-"..k, v2, conf["session_timeout"]) end - cache:set(user.."-"..k.."|"..k2, v2, conf["session_max_timeout"]) - end - else - cache:set(user.."-"..k, v, conf["session_timeout"]) - end - end - end - else - -- Else, just revalidate session for another day by default - password = cache:get(user.."-password") - -- Here we don't use set method to avoid strange logout - -- See https://github.com/YunoHost/issues/issues/1830 - cache:replace(user.."-password", password, conf["session_timeout"]) - end -end - - --- Summarize email, aliases and forwards in a table for a specific user -function get_mails(user) - local mails = { mail = "", mailalias = {}, maildrop = {} } - - -- default mail - mails["mail"] = cache:get(user.."-mail") - - -- mail aliases - if cache:get(user.."-mail|2") then - local i = 2 - while cache:get(user.."-mail|"..i) do - table.insert(mails["mailalias"], cache:get(user.."-mail|"..i)) - i = i + 1 - end - end - - -- mail forward - if cache:get(user.."-maildrop|2") then - local i = 2 - while cache:get(user.."-maildrop|"..i) do - table.insert(mails["maildrop"], cache:get(user.."-maildrop|"..i)) - i = i + 1 - end - end - return mails -end - - --- Yo dawg, this enables SSOwat to serve files in HTTP in an HTTP server --- Much reliable, very solid. --- --- Takes an URI, and returns file content with the proper HTTP headers. --- It is used to render the SSOwat portal *only*. -function serve(uri, cache) - - logger.debug("Serving portal uri "..uri) - - rel_path = string.gsub(uri, conf["portal_path"], "/") - - -- Load login.html as index - if rel_path == "/" then - if is_logged_in then - rel_path = "/portal.html" - else - rel_path = "/login.html" - end - end - - -- Access to directory root: forbidden - if string.ends(rel_path, "/") then - return ngx.exit(ngx.HTTP_FORBIDDEN) - end - - -- Try to get file content - local content = read_file(script_path.."portal"..rel_path) - if not content then - return ngx.exit(ngx.HTTP_NOT_FOUND) - end - - -- Extract file extension - _, file, ext = string.match(rel_path, "(.-)([^\\/]-%.?([^%.\\/]*))$") - - -- Associate to MIME type - mime_types = { - html = "text/html", - ms = "text/html", - js = "text/javascript", - map = "text/javascript", - css = "text/css", - gif = "image/gif", - jpg = "image/jpeg", - png = "image/png", - svg = "image/svg+xml", - ico = "image/vnd.microsoft.icon", - woff = "font/woff", - woff2 = "font/woff2", - ttf = "font/ttf", - json = "application/json" - } - - -- Allow .ms to specify mime type - mime = ext - if ext == "ms" then - subext = string.match(file, "^.+%.(.+)%.ms$") - if subext then - mime = subext - end - end - - -- Set Content-Type - if mime_types[mime] then - ngx.header["Content-Type"] = mime_types[mime] - else - ngx.header["Content-Type"] = "text/plain" - end - - -- Render as mustache - if ext == "html" then - local data = get_data_for(file) - local rendered = lustache:render(read_file(script_path.."portal/header.ms"), data) - rendered = rendered..lustache:render(content, data) - content = rendered..lustache:render(read_file(script_path.."portal/footer.ms"), data) - elseif ext == "ms" then - local data = get_data_for(file) - content = lustache:render(content, data) - elseif uri == "/ynh_userinfo.json" then - local data = get_data_for(file) - content = json.encode(data) - cache = "dynamic" - end - - -- Reset flash messages - flashs["fail"] = nil - flashs["win"] = nil - flashs["info"] = nil - - if cache == "static_asset" then - ngx.header["Cache-Control"] = "public, max-age=3600" - else - -- Ain't nobody got time for cache - ngx.header["Cache-Control"] = "no-cache" - end - - -- Print file content - ngx.say(content) - - -- Return 200 :-) - return ngx.exit(ngx.HTTP_OK) -end - - --- Simple controller that computes a data table to populate a specific view. --- The resulting data table typically contains the user information, the page --- title, the flash notifications' content and the translated strings. -function get_data_for(view) - local user = authUser - - -- For the login page we only need the page title - if view == "login.html" then - data = { - title = t("login"), - connected = false - } - - -- For those views, we may need user information - elseif view == "portal.html" - or view == "edit.html" - or view == "password.html" - or view == "ynh_userinfo.json" then - - -- Invalidate cache before loading these views. - -- Needed if the LDAP db is changed outside ssowat (from the cli for example). - -- Not doing it for ynhpanel.json only for performance reasons, - -- so the panel could show wrong first name, last name or main email address - -- TODO: What if we remove info during logout? - --if view ~= "ynhpanel.json" then - -- delete_user_info_cache(user) - --end - - -- Be sure cache is loaded - if user then - refresh_user_cache(user) - - local mails = get_mails(user) - data = { - connected = true, - theme = conf.theme, - portal_url = conf.portal_url, - uid = user, - cn = cache:get(user.."-cn"), - sn = cache:get(user.."-sn"), - givenName = cache:get(user.."-givenName"), - mail = mails["mail"], - mailalias = mails["mailalias"], - maildrop = mails["maildrop"], - app = {} - } - - local sorted_apps = {} - - -- Add user's accessible URLs using the ACLs. - -- It is typically used to build the app list. - for permission_name, permission in pairs(conf["permissions"]) do - -- We want to display a tile, and uris is not empty - if permission['show_tile'] and next(permission['uris']) ~= nil and element_is_in_table(user, permission["users"]) then - url = permission['uris'][1] - name = permission['label'] - - if ngx.var.host == conf["local_portal_domain"] then - url = string.gsub(url, conf["original_portal_domain"], conf["local_portal_domain"]) - end - - table.insert(sorted_apps, name) - table.sort(sorted_apps) - table.insert(data["app"], index_of(sorted_apps, name), { url = url, name = name }) - end - end - end - end - - -- Pass all the translated strings to the view (to use with t_) - for k, v in pairs(i18n[conf["default_language"]]) do - data["t_"..k] = (i18n[conf.lang] and i18n[conf.lang][k]) or v - end - - -- Pass flash notification content - data['flash_fail'] = {flashs["fail"]} - data['flash_win'] = {flashs["win"] } - data['flash_info'] = {flashs["info"]} - data['theme'] = conf["theme"] - - return data -end - --- this function is launched after a successful login --- it checked if the user password is stored using the most secure hashing --- algorithm available --- if it's not the case, it migrates the password to this new hash algorithm -function ensure_user_password_uses_strong_hash(ldap, user, password) - local current_hashed_password = nil - - for dn, attrs in ldap:search { - base = conf['ldap_group'], - scope = "onelevel", - sizelimit = 1, - filter = "("..conf['ldap_identifier'].."="..user..")", - attrs = {"userPassword"} - } do - current_hashed_password = attrs["userPassword"]:sub(0, 10) - end - - -- if the password is not hashed using sha-512, which is the strongest - -- available hash rehash it using that - -- Here "{CRYPT}" means "uses linux auth system" - -- "6" means "uses sha-512", any lower number mean a less strong algo (1 == md5) - if current_hashed_password:sub(0, 10) ~= "{CRYPT}$6$" then - local dn = conf["ldap_identifier"].."="..user..","..conf["ldap_group"] - local hashed_password = hash_password(password) - ldap:modify(dn, {'=', userPassword = hashed_password }) - end -end - --- Read result of a command after given it securely the password -function secure_cmd_password(cmd, password, start) - -- Check password validity - local tmp_file = os.tmpname() - local w_pwd = io.popen("("..cmd..") | tee -a "..tmp_file, 'w') - w_pwd:write(password) - -- This second write is just to validate the password question - -- Do not remove - w_pwd:write("") - w_pwd:close() - local r_pwd = io.open(tmp_file, 'r') - text = r_pwd:read "*a" - - -- Remove the extra end line - if text:sub(-1, -1) == "\n" then - text = text:sub(1, -2) - end - r_pwd:close() - os.remove(tmp_file) - return text -end - --- Compute the user modification POST request --- It has to update cached information and edit the LDAP user entry --- according to the changes detected. -function edit_user() - -- We need these calls since we are in a POST request - ngx.req.read_body() - local args = ngx.req.get_post_args() - - -- Ensure that user is logged in and has passed information - -- before continuing. - if is_logged_in and args - then - - -- Set HTTP status to 201 - ngx.status = ngx.HTTP_CREATED - local user = authUser - - -- In case of a password modification - -- TODO: split this into a new function - if string.ends(ngx.var.uri, "password.html") then - - -- Check current password against the cached one - if args.currentpassword - and args.currentpassword == cache:get(user.."-password") - then - -- and the new password against the confirmation field's content - if args.newpassword == args.confirm then - -- Check password validity - local result_msg = secure_cmd_password("python3 /usr/lib/python3/dist-packages/yunohost/utils/password.py", args.newpassword) - validation_error = true - if result_msg == nil or result_msg == "" then - validation_error = nil - end - if validation_error == nil then - - local dn = conf["ldap_identifier"].."="..user..","..conf["ldap_group"] - - -- Open the LDAP connection - local ldap = lualdap.open_simple(conf["ldap_host"], dn, args.currentpassword) - - local password = hash_password(args.newpassword) - - -- Modify the LDAP information - if ldap:modify(dn, {'=', userPassword = password }) then - if validation == nil then - flash("win", t("password_changed")) - else - flash("win", t(result_msg)) - end - - -- Reset the password cache - cache:set(user.."-password", args.newpassword, conf["session_timeout"]) - return redirect(conf.portal_url.."portal.html") - else - flash("fail", t("password_changed_error")) - end - else - flash("fail", t(result_msg)) - end - else - flash("fail", t("password_not_match")) - end - else - flash("fail", t("wrong_current_password")) - end - return redirect(conf.portal_url.."password.html") - - - -- In case of profile modification - -- TODO: split this into a new function - elseif string.ends(ngx.var.uri, "edit.html") then - - -- Check that needed arguments exist - if args.givenName and args.sn and args.mail then - - -- Unstack mailaliases - local mailalias = {} - if args["mailalias[]"] then - if type(args["mailalias[]"]) == "string" then - args["mailalias[]"] = {args["mailalias[]"]} - end - mailalias = args["mailalias[]"] - end - - -- Unstack mail forwards - local maildrop = {} - if args["maildrop[]"] then - if type(args["maildrop[]"]) == "string" then - args["maildrop[]"] = {args["maildrop[]"]} - end - maildrop = args["maildrop[]"] - end - - -- Limit domains per user: - -- This ensures that a user already has an email address or an - -- aliases that ends with a specific domain to claim new aliases - -- on this domain. - -- - -- I.E. You need to have xxx@domain.org to claim a - -- yyy@domain.org alias. - -- - local domains = {} - local ldap = lualdap.open_simple(conf["ldap_host"]) - for dn, attribs in ldap:search { - base = conf["ldap_group"], - scope = "onelevel", - sizelimit = 1, - filter = "(uid="..user..")", - attrs = {"mail"} - } do - -- Construct proper emails array - local mail_list = {} - local mail_attr = attribs["mail"] - if type(mail_attr) == "string" then - mail_list = { mail_attr } - elseif type(mail_attr) == "table" then - mail_list = mail_attr - end - - -- Filter configuration's domain list to keep only - -- "allowed" domains - for _, domain in ipairs(conf["domains"]) do - for k, mail in ipairs(mail_list) do - if string.ends(mail, "@"..domain) then - if not is_in_table(domains, domain) then - table.insert(domains, domain) - end - end - end - end - end - ldap:close() - - local rex = require "rex_pcre2" - local rex_flags = rex.flags() - local mail_re = rex.new([[^[\w\.\-+%]+@([^\W_A-Z]+([\-]*[^\W_A-Z]+)*\.)+([^\W\d_]{2,})$]], rex_flags.UTF8 + rex_flags.UCP) - - local mails = {} - - -- Build an LDAP filter so that we can ensure that email - -- addresses are used only once. - local filter = "(|" - table.insert(mailalias, 1, args.mail) - - -- Loop through all the aliases - for k, mail in ipairs(mailalias) do - if mail ~= "" then - -- Check the mail pattern - if not mail_re:match(mail) then - flash("fail", t("invalid_mail")..": "..mail) - return redirect(conf.portal_url.."edit.html") - - -- Check that the domain is known and allowed - else - local domain_valid = false - for _, domain in ipairs(domains) do - if string.ends(mail, "@"..domain) then - domain_valid = true - break - end - end - if domain_valid then - table.insert(mails, mail) - filter = filter.."(mail="..mail..")" - else - flash("fail", t("invalid_domain").." "..mail) - return redirect(conf.portal_url.."edit.html") - end - end - end - end - - -- filter should look like "(|(mail=my@mail.tld)(mail=my@mail2.tld))" - filter = filter..")" - - - -- For email forwards, we only need to check that they look - -- like actual emails - local drops = {} - for k, mail in ipairs(maildrop) do - if mail ~= "" then - if not mail_re:match(mail) then - flash("fail", t("invalid_mailforward")..": "..mail) - return redirect(conf.portal_url.."edit.html") - end - table.insert(drops, mail) - end - end - table.insert(drops, 1, user) - - - -- We now have a list of validated emails and forwards. - -- We need to check if there is a user with a claimed email - -- already before writing modifications to the LDAP. - local dn = conf["ldap_identifier"].."="..user..","..conf["ldap_group"] - local ldap = lualdap.open_simple(conf["ldap_host"], dn, cache:get(user.."-password")) - local cn = args.givenName.." "..args.sn - - for dn, attribs in ldap:search { - base = conf["ldap_group"], - scope = "onelevel", - filter = filter, - attrs = {conf["ldap_identifier"], "mail"} - } do - -- Another user with one of these emails has been found. - if attribs[conf["ldap_identifier"]] and attribs[conf["ldap_identifier"]] ~= user then - -- Construct proper emails array - local mail_list = {} - local mail_attr = attribs["mail"] - if type(mail_attr) == "string" then - mail_list = { mail_attr } - elseif type(mail_attr) == "table" then - mail_list = mail_attr - end - - for _, mail in ipairs(mail_list) do - if is_in_table(mails, mail) then - flash("fail", t("mail_already_used").." "..mail) - end - end - return redirect(conf.portal_url.."edit.html") - end - end - - -- No problem so far, we can write modifications to the LDAP - if ldap:modify(dn, {'=', cn = cn, - displayName = cn, - givenName = args.givenName, - sn = args.sn, - mail = mails, - maildrop = drops }) - then - delete_user_info_cache(user) - -- Ugly trick to force cache reloading - refresh_user_cache(user) - flash("win", t("information_updated")) - return redirect(conf.portal_url.."portal.html") - - else - flash("fail", t("user_saving_fail")) - end - else - flash("fail", t("missing_required_fields")) - end - return redirect(conf.portal_url.."edit.html") - end - end -end - --- hash the user password using sha-512 and using {CRYPT} to uses linux auth system --- because ldap doesn't support anything stronger than sha1 -function hash_password(password) - local hashed_password = secure_cmd_password("mkpasswd --method=sha-512", password) - hashed_password = "{CRYPT}"..hashed_password - return hashed_password -end - --- Compute the user login POST request --- It authenticates the user against the LDAP base then redirects to the portal -function login() - - -- We need these calls since we are in a POST request - ngx.req.read_body() - local args = ngx.req.get_post_args() - local uri_args = ngx.req.get_uri_args() - - args.user = string.lower(args.user) - - local user = authenticate(args.user, args.password) - if user then - ngx.status = ngx.HTTP_CREATED - set_auth_cookie(user, ngx.var.host) - else - ngx.status = ngx.HTTP_UNAUTHORIZED - flash("fail", t("wrong_username_password")) - end - - -- Forward the `r` URI argument if it exists to redirect - -- the user properly after a successful login. - if uri_args.r then - return redirect(conf.portal_url.."?r="..uri_args.r) - else - return redirect(conf.portal_url) - end -end - - --- Compute the user logout request --- It deletes session cached information to invalidate client side cookie --- information. -function logout() - - -- We need this call since we are in a POST request - local args = ngx.req.get_uri_args() - - -- Delete user cookie if logged in (that should always be the case) - if is_logged_in then - delete_cookie() - cache:delete("session_"..authUser) - cache:delete(authUser.."-"..conf["ldap_identifier"]) -- Ugly trick to reload cache - cache:delete(authUser.."-password") - delete_user_info_cache(authUser) - flash("info", t("logged_out")) - is_logged_in = false - end - - -- Redirect with the `r` URI argument if it exists or redirect to portal - if args.r then - return redirect(ngx.decode_base64(args.r)) - else - return redirect(conf.portal_url) - end -end - - --- Set cookie and redirect (needed to properly set cookie) -function redirect(url) - logger.debug("Redirecting to "..url) - -- For security reason we don't allow to redirect onto unknown domain - -- And if `uri_args.r` contains line break, someone is probably trying to - -- pass some additional headers - - -- This should cover the following cases: - -- https://malicious.domain.tld/foo/bar - -- http://malicious.domain.tld/foo/bar - -- https://malicious.domain.tld:1234/foo - -- malicious.domain.tld/foo/bar - -- (/foo/bar, in which case no need to make sure it's prefixed with https://) - if not string.starts(url, "/") and not string.starts(url, "http://") and not string.starts(url, "https://") then - url = "https://"..url - end - local is_known_domain = string.starts(url, "/") - for _, domain in ipairs(conf["domains"]) do - if is_known_domain then - break - end - -- Replace - character to %- because - is a special char for regex in lua - domain = string.gsub(domain, "%-","%%-") - is_known_domain = is_known_domain or url:match("^https?://"..domain.."/?") ~= nil - end - if string.match(url, "(.*)\n") or not is_known_domain then - logger.debug("Unauthorized redirection to "..url) - flash("fail", t("redirection_error_invalid_url")) - url = conf.portal_url - end - return ngx.redirect(url) -end - - --- Set cookie and go on with the response (needed to properly set cookie) -function pass() - logger.debug("Allowing to pass through "..ngx.var.uri) - - -- When we are in the SSOwat portal, we need a default `content-type` - if string.ends(ngx.var.uri, "/") - or string.ends(ngx.var.uri, ".html") - or string.ends(ngx.var.uri, ".htm") - then - ngx.header["Content-Type"] = "text/html" - end - - return -end diff --git a/init.lua b/init.lua index 6993e53..06a47f8 100644 --- a/init.lua +++ b/init.lua @@ -3,65 +3,52 @@ -- -- This is the initialization file of SSOwat. It is called once at the Nginx -- server's start. --- Consequently, all the variables declared (along with libraries and +-- Consequently, all the variables declared (along with libraries and -- translations) in this file will be *persistent* from one HTTP request to -- another. -- --- Path of the configuration -conf_path = "/etc/ssowat/conf.json" -log_file = "/var/log/nginx/ssowat.log" - -- Remove prepending '@' & trailing 'init.lua' script_path = string.sub(debug.getinfo(1).source, 2, -9) - -- Include local libs in package.path package.path = package.path .. ";"..script_path.."?.lua" --- Load libraries -local json = require "json" -local lualdap = require "lualdap" -local math = require "math" -local lfs = require "lfs" -local socket = require "socket" -local config = require "config" -lustache = require "lustache" +-- Load cookie secret +-- IMPORTANT (though to be confirmed?) +-- in this context, the code is ran as root therefore we don't have to +-- add www-data in the file permissions, which could otherwise lead +-- to comprised apps running with the www-data group to read the secret file? +local config = require("config") +cookie_secret = config.get_cookie_secret() + +-- +-- Init logger +-- + +local log_file = "/var/log/nginx/ssowat.log" -- Make sure the log file exists and we can write in it io.popen("touch "..log_file) io.popen("chown www-data "..log_file) io.popen("chmod u+w "..log_file) --- Persistent shared table -flashs = {} -i18n = {} +local Logging = require("logging") +local appender = function(self, level, message) --- convert a string to a hex -function tohex(str) - return (str:gsub('.', function (c) - return string.format('%02X', string.byte(c)) - end)) + -- Output to log file + local fp = io.open(log_file, "a") + local str = string.format("[%-6s%s] %s\n", level:upper(), os.date(), message) + fp:write(str) + fp:close() + + return true end --- Efficient function to get a random string -function random_string() - local length = 64 - local random_bytes = io.open("/dev/urandom"):read(length); - if string.len(random_bytes) ~= length then - error("Not enough random bytes read") - end - return tohex(random_bytes); -end +logger = Logging.new(appender) + +-- FIXME : how to set logging level ? +--logger:setLevel(logger.DEBUG) -- FIXME --- Load translations in the "i18n" above table -local locale_dir = script_path.."portal/locales/" -for file in lfs.dir(locale_dir) do - if string.sub(file, -4) == "json" then - local lang = string.sub(file, 1, 2) - local locale_file = io.open(locale_dir..file, "r") - i18n[lang] = json.decode(locale_file:read("*all")) - end -end -- You should see that in your Nginx error logs by default ngx.log(ngx.INFO, "SSOwat ready") diff --git a/log.lua b/log.lua deleted file mode 100644 index 9614a66..0000000 --- a/log.lua +++ /dev/null @@ -1,84 +0,0 @@ --- --- log.lua --- --- Copyright (c) 2016 rxi --- --- This library is free software; you can redistribute it and/or modify it --- under the terms of the MIT license. See LICENSE for details. --- - -local log = { _version = "0.1.0" } -local conf = config.get_config() - -log.usecolor = true -log.level = conf.logging - -local modes = { - { name = "trace", color = "\27[34m", }, - { name = "debug", color = "\27[36m", }, - { name = "info", color = "\27[32m", }, - { name = "warn", color = "\27[33m", }, - { name = "error", color = "\27[31m", }, - { name = "fatal", color = "\27[35m", }, -} - - -local levels = {} -for i, v in ipairs(modes) do - levels[v.name] = i -end - - -local round = function(x, increment) - increment = increment or 1 - x = x / increment - return (x > 0 and math.floor(x + .5) or math.ceil(x - .5)) * increment -end - - -local _tostring = tostring - -local tostring = function(...) - local t = {} - for i = 1, select('#', ...) do - local x = select(i, ...) - if type(x) == "number" then - x = round(x, .01) - end - t[#t + 1] = _tostring(x) - end - return table.concat(t, " ") -end - - -for i, x in ipairs(modes) do - local nameupper = x.name:upper() - log[x.name] = function(...) - - -- Return early if we're below the log level - if i < levels[log.level] then - return - end - - local msg = tostring(...) - local info = debug.getinfo(2, "Sl") - - -- Output to console - print(string.format("%s[%-6s%s]%s %s", - log.usecolor and x.color or "", - nameupper, - os.date("%H:%M:%S"), - log.usecolor and "\27[0m" or "", - msg)) - - -- Output to log file - local fp = io.open(log_file, "a") - local str = string.format("[%-6s%s] %s\n", - nameupper, os.date(), msg) - fp:write(str) - fp:close() - end -end - - -return log diff --git a/lustache.lua b/lustache.lua deleted file mode 100644 index 94d8a25..0000000 --- a/lustache.lua +++ /dev/null @@ -1,29 +0,0 @@ --- lustache: Lua mustache template parsing. --- Copyright 2013 Olivine Labs, LLC --- MIT Licensed. - -module('lustache', package.seeall) - -local string_gmatch = string.gmatch - -function string.split(str, sep) - local out = {} - for m in string_gmatch(str, "[^"..sep.."]+") do out[#out+1] = m end - return out -end - -local lustache = { - name = "lustache", - version = "1.3.1-0", - renderer = require("lustache.renderer"):new(), -} - -return setmetatable(lustache, { - __index = function(self, idx) - if self.renderer[idx] then return self.renderer[idx] end - end, - __newindex = function(self, idx, val) - if idx == "partials" then self.renderer.partials = val end - if idx == "tags" then self.renderer.tags = val end - end -}) diff --git a/lustache/LICENSE b/lustache/LICENSE deleted file mode 100644 index b16c8f8..0000000 --- a/lustache/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License - -Copyright (c) 2012 Olivine Labs - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lustache/context.lua b/lustache/context.lua deleted file mode 100644 index f8ec1a5..0000000 --- a/lustache/context.lua +++ /dev/null @@ -1,66 +0,0 @@ -local string_find, string_split, tostring, type = - string.find, string.split, tostring, type - -local context = {} -context.__index = context - -function context:clear_cache() - self.cache = {} -end - -function context:push(view) - return self:new(view, self) -end - -function context:lookup(name) - local value = self.cache[name] - - if not value then - if name == "." then - value = self.view - else - local context = self - - while context do - if string_find(name, ".") > 0 then - local names = string_split(name, ".") - local i = 0 - - value = context.view - - if(type(value)) == "number" then - value = tostring(value) - end - - while value and i < #names do - i = i + 1 - value = value[names[i]] - end - else - value = context.view[name] - end - - if value then - break - end - - context = context.parent - end - end - - self.cache[name] = value - end - - return value -end - -function context:new(view, parent) - local out = { - view = view, - parent = parent, - cache = {}, - } - return setmetatable(out, context) -end - -return context diff --git a/lustache/renderer.lua b/lustache/renderer.lua deleted file mode 100644 index 94e0cfb..0000000 --- a/lustache/renderer.lua +++ /dev/null @@ -1,388 +0,0 @@ -local Scanner = require "lustache.scanner" -local Context = require "lustache.context" - -local error, ipairs, loadstring, pairs, setmetatable, tostring, type = - error, ipairs, loadstring, pairs, setmetatable, tostring, type -local math_floor, math_max, string_find, string_gsub, string_split, string_sub, table_concat, table_insert, table_remove = - math.floor, math.max, string.find, string.gsub, string.split, string.sub, table.concat, table.insert, table.remove - -local patterns = { - white = "%s*", - space = "%s+", - nonSpace = "%S", - eq = "%s*=", - curly = "%s*}", - tag = "[#\\^/>{&=!]" -} - -local html_escape_characters = { - ["&"] = "&", - ["<"] = "<", - [">"] = ">", - ['"'] = """, - ["'"] = "'", - ["/"] = "/" -} - -local function is_array(array) - if type(array) ~= "table" then return false end - local max, n = 0, 0 - for k, _ in pairs(array) do - if not (type(k) == "number" and k > 0 and math_floor(k) == k) then - return false - end - max = math_max(max, k) - n = n + 1 - end - return n == max -end - --- Low-level function that compiles the given `tokens` into a --- function that accepts two arguments: a Context and a --- Renderer. - -local function compile_tokens(tokens, originalTemplate) - local subs = {} - - local function subrender(i, tokens) - if not subs[i] then - local fn = compile_tokens(tokens, originalTemplate) - subs[i] = function(ctx, rnd) return fn(ctx, rnd) end - end - return subs[i] - end - - local function render(ctx, rnd) - local buf = {} - local token, section - for i, token in ipairs(tokens) do - local t = token.type - buf[#buf+1] = - t == "#" and rnd:_section( - token, ctx, subrender(i, token.tokens), originalTemplate - ) or - t == "^" and rnd:_inverted( - token.value, ctx, subrender(i, token.tokens) - ) or - t == ">" and rnd:_partial(token.value, ctx, originalTemplate) or - (t == "{" or t == "&") and rnd:_name(token.value, ctx, false) or - t == "name" and rnd:_name(token.value, ctx, true) or - t == "text" and token.value or "" - end - return table_concat(buf) - end - return render -end - -local function escape_tags(tags) - - return { - string_gsub(tags[1], "%%", "%%%%").."%s*", - "%s*"..string_gsub(tags[2], "%%", "%%%%"), - } -end - -local function nest_tokens(tokens) - local tree = {} - local collector = tree - local sections = {} - local token, section - - for i,token in ipairs(tokens) do - if token.type == "#" or token.type == "^" then - token.tokens = {} - sections[#sections+1] = token - collector[#collector+1] = token - collector = token.tokens - elseif token.type == "/" then - if #sections == 0 then - error("Unopened section: "..token.value) - end - - -- Make sure there are no open sections when we're done - section = table_remove(sections, #sections) - - if not section.value == token.value then - error("Unclosed section: "..section.value) - end - - section.closingTagIndex = token.startIndex - - if #sections > 0 then - collector = sections[#sections].tokens - else - collector = tree - end - else - collector[#collector+1] = token - end - end - - section = table_remove(sections, #sections) - - if section then - error("Unclosed section: "..section.value) - end - - return tree -end - --- Combines the values of consecutive text tokens in the given `tokens` array --- to a single token. -local function squash_tokens(tokens) - local out, txt = {}, {} - local txtStartIndex, txtEndIndex - for _, v in ipairs(tokens) do - if v.type == "text" then - if #txt == 0 then - txtStartIndex = v.startIndex - end - txt[#txt+1] = v.value - txtEndIndex = v.endIndex - else - if #txt > 0 then - out[#out+1] = { type = "text", value = table_concat(txt), startIndex = txtStartIndex, endIndex = txtEndIndex } - txt = {} - end - out[#out+1] = v - end - end - if #txt > 0 then - out[#out+1] = { type = "text", value = table_concat(txt), startIndex = txtStartIndex, endIndex = txtEndIndex } - end - return out -end - -local function make_context(view) - if not view then return view end - return getmetatable(view) == Context and view or Context:new(view) -end - -local renderer = { } - -function renderer:clear_cache() - self.cache = {} - self.partial_cache = {} -end - -function renderer:compile(tokens, tags, originalTemplate) - tags = tags or self.tags - if type(tokens) == "string" then - tokens = self:parse(tokens, tags) - end - - local fn = compile_tokens(tokens, originalTemplate) - - return function(view) - return fn(make_context(view), self) - end -end - -function renderer:render(template, view, partials) - if type(self) == "string" then - error("Call mustache:render, not mustache.render!") - end - - if partials then - -- remember partial table - -- used for runtime lookup & compile later on - self.partials = partials - end - - if not template then - return "" - end - - local fn = self.cache[template] - - if not fn then - fn = self:compile(template, self.tags, template) - self.cache[template] = fn - end - - return fn(view) -end - -function renderer:_section(token, context, callback, originalTemplate) - local value = context:lookup(token.value) - - if type(value) == "table" then - if is_array(value) then - local buffer = "" - - for i,v in ipairs(value) do - buffer = buffer .. callback(context:push(v), self) - end - - return buffer - end - - return callback(context:push(value), self) - elseif type(value) == "function" then - local section_text = string_sub(originalTemplate, token.endIndex+1, token.closingTagIndex - 1) - - local scoped_render = function(template) - return self:render(template, context) - end - - return value(section_text, scoped_render) or "" - else - if value then - return callback(context, self) - end - end - - return "" -end - -function renderer:_inverted(name, context, callback) - local value = context:lookup(name) - - -- From the spec: inverted sections may render text once based on the - -- inverse value of the key. That is, they will be rendered if the key - -- doesn't exist, is false, or is an empty list. - - if value == nil or value == false or (type(value) == "table" and is_array(value) and #value == 0) then - return callback(context, self) - end - - return "" -end - -function renderer:_partial(name, context, originalTemplate) - local fn = self.partial_cache[name] - - -- check if partial cache exists - if (not fn and self.partials) then - - local partial = self.partials[name] - if (not partial) then - return "" - end - - -- compile partial and store result in cache - fn = self:compile(partial, nil, originalTemplate) - self.partial_cache[name] = fn - end - return fn and fn(context, self) or "" -end - -function renderer:_name(name, context, escape) - local value = context:lookup(name) - - if type(value) == "function" then - value = value(context.view) - end - - local str = value == nil and "" or value - str = tostring(str) - - if escape then - return string_gsub(str, '[&<>"\'/]', function(s) return html_escape_characters[s] end) - end - - return str -end - --- Breaks up the given `template` string into a tree of token objects. If --- `tags` is given here it must be an array with two string values: the --- opening and closing tags used in the template (e.g. ["<%", "%>"]). Of --- course, the default is to use mustaches (i.e. Mustache.tags). -function renderer:parse(template, tags) - tags = tags or self.tags - local tag_patterns = escape_tags(tags) - local scanner = Scanner:new(template) - local tokens = {} -- token buffer - local spaces = {} -- indices of whitespace tokens on the current line - local has_tag = false -- is there a {{tag} on the current line? - local non_space = false -- is there a non-space char on the current line? - - -- Strips all whitespace tokens array for the current line if there was - -- a {{#tag}} on it and otherwise only space - local function strip_space() - if has_tag and not non_space then - while #spaces > 0 do - table_remove(tokens, table_remove(spaces)) - end - else - spaces = {} - end - has_tag = false - non_space = false - end - - local type, value, chr - - while not scanner:eos() do - local start = scanner.pos - - value = scanner:scan_until(tag_patterns[1]) - - if value then - for i = 1, #value do - chr = string_sub(value,i,i) - - if string_find(chr, "%s+") then - spaces[#spaces+1] = #tokens + 1 - else - non_space = true - end - - tokens[#tokens+1] = { type = "text", value = chr, startIndex = start, endIndex = start } - start = start + 1 - if chr == "\n" then - strip_space() - end - end - end - - if not scanner:scan(tag_patterns[1]) then - break - end - - has_tag = true - type = scanner:scan(patterns.tag) or "name" - - scanner:scan(patterns.white) - - if type == "=" then - value = scanner:scan_until(patterns.eq) - scanner:scan(patterns.eq) - scanner:scan_until(tag_patterns[2]) - elseif type == "{" then - local close_pattern = "%s*}"..tags[2] - value = scanner:scan_until(close_pattern) - scanner:scan(patterns.curly) - scanner:scan_until(tag_patterns[2]) - else - value = scanner:scan_until(tag_patterns[2]) - end - - if not scanner:scan(tag_patterns[2]) then - error("Unclosed tag at " .. scanner.pos) - end - - tokens[#tokens+1] = { type = type, value = value, startIndex = start, endIndex = scanner.pos - 1 } - if type == "name" or type == "{" or type == "&" then - non_space = true --> what does this do? - end - - if type == "=" then - tags = string_split(value, patterns.space) - tag_patterns = escape_tags(tags) - end - end - - return nest_tokens(squash_tokens(tokens)) -end - -function renderer:new() - local out = { - cache = {}, - partial_cache = {}, - tags = {"{{", "}}"} - } - return setmetatable(out, { __index = self }) -end - -return renderer diff --git a/lustache/scanner.lua b/lustache/scanner.lua deleted file mode 100644 index 0673df1..0000000 --- a/lustache/scanner.lua +++ /dev/null @@ -1,57 +0,0 @@ -local string_find, string_match, string_sub = - string.find, string.match, string.sub - -local scanner = {} - --- Returns `true` if the tail is empty (end of string). -function scanner:eos() - return self.tail == "" -end - --- Tries to match the given regular expression at the current position. --- Returns the matched text if it can match, `null` otherwise. -function scanner:scan(pattern) - local match = string_match(self.tail, pattern) - - if match and string_find(self.tail, pattern) == 1 then - self.tail = string_sub(self.tail, #match + 1) - self.pos = self.pos + #match - - return match - end - -end - --- Skips all text until the given regular expression can be matched. Returns --- the skipped string, which is the entire tail of this scanner if no match --- can be made. -function scanner:scan_until(pattern) - - local match - local pos = string_find(self.tail, pattern) - - if pos == nil then - match = self.tail - self.pos = self.pos + #self.tail - self.tail = "" - elseif pos == 1 then - match = nil - else - match = string_sub(self.tail, 1, pos - 1) - self.tail = string_sub(self.tail, pos) - self.pos = self.pos + #match - end - - return match -end - -function scanner:new(str) - local out = { - str = str, - tail = str, - pos = 1 - } - return setmetatable(out, { __index = self } ) -end - -return scanner diff --git a/portal/assets/css/ynh_overlay.css b/portal/assets/css/ynh_overlay.css deleted file mode 100644 index 1604004..0000000 --- a/portal/assets/css/ynh_overlay.css +++ /dev/null @@ -1,182 +0,0 @@ -/* -=============================================================================== - This file contains CSS rules loaded on all apps page (*if* the app nginx's - conf does include the appropriate snippet) for the small YunoHost button in - bottom-right corner + portal overlay. - - The yunohost button corresponds to : #ynh-overlay-switch - The yunohost portal overlay / iframe corresponds to : #ynh-overlay - - BE CAREFUL that you should *not* add too-general rules that apply to - non-yunohost elements (for instance all 'a' or 'p' elements...) as it will - likely break app's rendering -=============================================================================== -*/ - -/* ****************************************************************** - General -******************************************************************* */ - -html.ynh-panel-active { - /* Disable any scrolling on app */ - overflow: hidden; - -} - -body { - overflow-y: auto; -} - -#ynh-overlay-switch, -#ynh-overlay-switch *, -#ynh-overlay, -#ynh-overlay * { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - - -/* ****************************************************************** - Button -******************************************************************* */ -#ynh-overlay-switch { - display: block; - position: fixed; - z-index: 10000000; - bottom: 20px; - right: 35px; - width: 100px; - height: 90px; - padding: 12px; - border: 12px solid #41444f; - border-radius: 5px; - background: #41444f; - background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE1LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluICAtLT4KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIiBbCgk8IUVOVElUWSBuc19mbG93cyAiaHR0cDovL25zLmFkb2JlLmNvbS9GbG93cy8xLjAvIj4KXT4KPHN2ZyB2ZXJzaW9uPSIxLjEiCgkgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6YT0iaHR0cDovL25zLmFkb2JlLmNvbS9BZG9iZVNWR1ZpZXdlckV4dGVuc2lvbnMvMy4wLyIKCSB4PSIwcHgiIHk9IjBweCIgd2lkdGg9Ijk4cHgiIGhlaWdodD0iODVweCIgdmlld0JveD0iLTAuMjUgLTAuMjUgOTggODUiCgkgb3ZlcmZsb3c9InZpc2libGUiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgLTAuMjUgLTAuMjUgOTggODUiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8ZGVmcz4KPC9kZWZzPgo8cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNOTcsNTFjLTIuMDIsNC45OC04LjMzLDUuNjctMTQsN2MtMC42MDksNi4yOSwzLjA1LDEwLjk1LTEsMTZjLTYuNDEtMC4yNi03LjQ3MS01Ljg1OS03LTEzYy0xLDAtMiwwLTMsMAoJYy0yLjA5LDIuNzcsMC45LDQuNTIsMCw4Yy0xLjEyLDQuMzQtNy44OCw3LjkxLTExLDdjLTIuMTgtMC42NDEtNS45Ni02LjYzLTUtMTJjMi44Mi0yLjcxLDIuNzYsMy4xMiw2LDNjNS4wNS03Ljg0LTkuNjMtOC41NS04LTE3CgljMS4yNC02LjQyLDExLjY2LTkuNjYsMTUtMWMxLjU0LDQuMjEtNS4xNywwLjE2LTUsM2MtMC4yNzksMS42MiwwLjk1LDEuNzIsMSwzYzIuNTIsMC43NywxLjY4LTIuMTYsMy0zYzEuODU5LTEuMTcsMy4wOS0wLjc1LDYtMQoJYzIuNDUtMi41NSwxLjA4LTguOTIsNC0xMWMzLjg3LDAuNDYsNi4wOCwyLjU5LDYsN0M5MS4wMSw0Ni4xMDksOTQuMyw0Ni4wNSw5Nyw1MXoiLz4KPHBhdGggZmlsbD0iI0ZGRkZGRiIgZD0iTTg3LDEzYzAuNjA5LDMuMjEsMi4zMiw0Ljk4LDIsOGMtMC4zNCwzLjIxLTIuOSw4LjgzLTQsOWMtMS4xNywwLjE4LTEuMzQsMS43OC0yLDIKCWMtNC42NiwxLjU3LTEyLjM5MS0xLjQ4LTE0LTdjLTEuMTYtMy45NywxLjktMTMuMzcsNC0xN2MxLjMtMi4yNSwxLjIyMS0yLjk5LDUtNGMyLjQxLTAuNjUsMy42NS0yLjI1LDYsMAoJYzAuNDcxLDAuNDUsMS4zLDAuNDksMS44NSwwLjg5Yy0wLjE5OSwwLDIsMy4xNCwyLjE1LDQuMTFDODguMzIsMTEuMDcsODYuNzcsMTEuNzgsODcsMTN6IE03OSwyMmMxLjc3OS0xLjg5LDMuMjktNC4wNCwzLTgKCUM3Ny40OSwxMi4zMyw3NC42NywyMS4zLDc5LDIyeiIvPgo8cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNNjcsMjFjLTAuMDcsNS44MSwyLjQ4LDEwLjcsMCwxNWMtNi43MywxLjA2LTcuMjQtNC4xLTExLTZjLTEuOTM5LDEuMzktMS40OSw1LjE4LTMsNwoJYy0zLjc4LDAuNDQtNC42OS0xLjk3LTctM2MyLjQ3LTcuODEsMS4yNi0xOC45OCwyLTI2YzguNTgtMC41OCw3LjY4LDguMzIsMTIsMTJjMC41Mi00LjM0LTAuMzU5LTE1LjUyLDMtMjAKCUM3MC4zMywzLjI5LDY3LjA5LDEyLjk5LDY3LDIxeiIvPgo8cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNNTIsNTVjMS45Myw4LjQxLDAuMTIsMjIuNjg5LTEyLDIwYy0xLjU5LTAuMzUtOC40Mi01LjIyLTktN2MtMS42Mi01LDAuMzQtMTMuMzQsMy0xNgoJQzM5LjAzLDQ2Ljk3LDQ1LjQ4LDUwLjM1OSw1Miw1NXogTTM5LDY2YzQuNTUsMC45Niw2LjMtNC4yLDQtN0MzOS4zNyw1OS4wMywzOC42MSw2MS45MzksMzksNjZ6Ii8+CjxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0zOSw4YzUuNTgsMC45LDYuNCw2LjgxLDUsMTVjLTEuNDMsOC4zOC0zLjAyLDE0LjU5LTksMTVjLTkuNTcsMC42NS0xMi4yNS0xNi42OS05LTI5CgljOC4zMiwxLjI3LDYuNTksMTAuMzYsNiwxN2MyLjcxLDAuODMsMi4yLTAuODUsMy0yQzM3LjA1LDIxLjA0LDM3LjgyLDEzLjYxLDM5LDh6Ii8+CjxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0yOCw2MmMwLjEsNS42Nyw0LjQsMTEuMzMsMiwxN2MtNC4zMi0xLjAxLTYuNTctNC4wOS05LTdjLTMuMTUtMC40OC0yLjI2LDMuMDctNiwyCgljLTAuNjcsNS4wNjEsMi4yOSw3LjU3LTEsMTBjLTQuNy0wLjYzLTYuNjYtNC04LThjLTIuNjEtMS4zOC01LjQ4LTIuNTItNi02YzAuMTQtMy41Myw0LjQ4LTIuODUsNy00YzAuNDctNS41My0xLjQxLTEzLjQxLDItMTYKCWM4LjMxLDAuNDksOC4yMSw3LjEzLDcsMTVjNC4zNiwwLjI5LDQuOTQtNC4zNSw1LTdjMC4wNi0yLjQzLTEuODItOC4yNiwyLTExYzMuMDYtMC43MywyLjk0LDEuNzMsNiwxCglDMzIuMzUsNTIuNywyNy45Miw1Ny40MzksMjgsNjJ6Ii8+CjxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0yNCwxMmMxLjA3LDcuMDctMy44Niw4LjE0LTYsMTJjMC4yMSw2Ljg4LTAuNDcsMTIuODYtMiwxOGMtNS44Ni0xLjMyLTguNy0xMC4zOC02LTE3CgljLTAuMzMtMy41Mi01LjI2LTQuMjItNy04Yy0wLjMtMC42Ni0wLjQ3LTQuNDMtMS03QzEuMDksNS42MywwLjU1LDQuMzEsMywxYzguMTYtMC40OSw3LjIxLDguMTMsOSwxNGM1LjA1LDAuMzksMy45MS01LjQyLDgtNgoJQzIwLjk4LDEwLjM1LDIyLjY3LDExLDI0LDEyeiIvPgo8L3N2Zz4K); - background-position: center center; - background-repeat: no-repeat; - background-size: contain; - opacity: 0.7; -} -/*#ynh-overlay-switch.visible,*/ -#ynh-overlay-switch:hover { - background-color: #41444f; - border-color: #41444f; - background-color: #111; - border-color: #111; -} - - -/* ****************************************************************** - Overlay -******************************************************************* */ - -/* Background */ -#ynh-overlay { - overflow-y: hidden; - position: fixed; - top:0; - left: 0; - width: 100%; - height: 100%; - z-index: 9999999; - display: none; - border: none; - color:#fff; - background: #41444F; - transition: all 0.2s ease; - -moz-transition: all 0.2s ease; - -webkit-transition: all 0.2s ease; -} - - -/* ****************************************************************** - Animation -******************************************************************* */ - -/*FadeIn*/ -@-webkit-keyframes ynhFadeIn { - 0% { - visibility: hidden; - opacity:0; - } - 100% { - visibility: visible; - opacity: 1; - } -} -@keyframes ynhFadeIn { - 0% { - visibility: hidden; - opacity: 0; - } - 100% { - visibility: visible; - opacity: 1; - } -} - -.ynh-fadeIn { - -webkit-animation-name: ynhFadeIn; - animation-name: ynhFadeIn; - -webkit-animation-duration: 0.5s; - animation-duration: 0.5s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; - -webkit-animation-timing-function: cubic-bezier(0.165, 0.840, 0.440, 1.000); - animation-timing-function: cubic-bezier(0.165, 0.840, 0.440, 1.000); -} -/* -.ynh-fadeIn.ynh-delay { - animation-delay: 0.5s; - -webkit-animation-delay: 0.5s; -} -*/ - -/*FadeOut*/ -@-webkit-keyframes ynhFadeOut { - 0% { - visibility: visible; - opacity: 1; - } - 100% { - visibility: hidden; - opacity: 0; - } -} -@keyframes ynhFadeOut { - 0% { - visibility: visible; - opacity: 1; - } - 100% { - visibility: hidden; - opacity: 0; - } -} -.ynh-fadeOut { - -webkit-animation-name: ynhFadeOut; - animation-name: ynhFadeOut; - -webkit-animation-duration: 0.2s; - animation-duration: 0.2s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; -} -/* -.ynh-fadeOut.ynh-delay { - animation-delay: 0.5s; - -webkit-animation-delay: 0.5s; -} -*/ - - -/* ****************************************************************** - Media Queries -******************************************************************* */ - -@media screen and (max-width: 500px) { - #ynh-overlay-switch { - width: 80px; - height: 75px; - } -} \ No newline at end of file diff --git a/portal/assets/css/ynh_portal.css b/portal/assets/css/ynh_portal.css deleted file mode 100644 index 4a580a6..0000000 --- a/portal/assets/css/ynh_portal.css +++ /dev/null @@ -1,828 +0,0 @@ -/* -=============================================================================== - This file contain CSS rules loaded on the YunoHost user portal. -=============================================================================== -*/ - -/* ========================================================================== - 0 = Fonts - 1 = Global - 2 = Apps - 3 = User - 4 = Form - 5 = Footer - 6 = Colors - 7 = Internet Explorer - ========================================================================== */ - -/* ========================================================================== - 0 = Fonts - ========================================================================== */ -@font-face { - font-family: 'ynh_ssowat'; - src: url('../fonts/ynh_ssowat/ynh_ssowat.eot'); -} - -@font-face { - font-family: 'ynh_ssowat'; - src: url(data:application/x-font-ttf;charset=utf-8;base64,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) format('truetype'), - url(data:application/font-woff;charset=utf-8;base64,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) format('woff'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'source_sans_probold'; - src: url('../fonts/sourcesanspro-bold-webfont.eot'); - } - -@font-face { - font-family: 'source_sans_probold'; - src: url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff'), - url('../fonts/sourcesanspro-bold-webfont.ttf') format('truetype'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'source_sans_proregular'; - src: url('../fonts/sourcesanspro-regular-webfont.eot'); - } - -@font-face { - font-family: 'source_sans_proregular'; - src: url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff'), - url('../fonts/sourcesanspro-regular-webfont.ttf') format('truetype'); - font-weight: normal; - font-style: normal; -} - -/* ========================================================================== - 1 = Global - ========================================================================== */ - -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -html { - font-family: sans-serif; /* 1 */ - -ms-text-size-adjust: 100%; /* 2 */ - -webkit-text-size-adjust: 100%; /* 2 */ - height: 100%; -} - -body { - background: #41444f; - font-family: 'source_sans_proregular'; - font-size: 1em; - line-height:1.5; - margin: 0; - padding: 0; - height: 100%; - top: 0; - position: absolute; - width: 100%; -} - -h1:first-child, -h2:first-child, -h3:first-child, -h4:first-child, -h5:first-child, -h6:first-child, -p:first-child, -ul:first-child, -ol:first-child, -dl:first-child{ - margin-top: 0; -} - - - - -/* Responsive image */ -img { - max-width: 100%; - height :auto; -} - -.element-invisible { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; -} - - -/* Layout */ -.content { - padding: 2%; -} - -.logged .content { - width: 100%; - height: 100%; - overflow: auto; /* scroll .content, not body for easier background customization */ -} - -.ynh-wrapper { - width: 90%; - margin: 2% 5%; - position: relative; - z-index: 1; -} -.ynh-wrapper:before, -.ynh-wrapper:after {content: " ";display: table;} -.ynh-wrapper:after {clear: both;} - -/* Logo */ -.ynh-logo { - opacity: 0.7; - margin-top: 6em; - width: 100%; - height: 9em; - background-image: url("../img/logo-ynh-white.svg"); - background-repeat: no-repeat; - background-position: center 100%; - background-size: contain; -} - -.logged .ynh-logo { - position: fixed; - width: 5em; - height: 5em; - bottom: 20px; - right: 20px; - z-index: 0; - opacity: 0.7; - background-position: center center; -} - -.ynh-panel-active .ynh-logo { - display: none; -} - -.in_app_overlay .ynh-logo { - display: none; -} - -/* messages */ -.messages { - color: #FFF; - margin-bottom: 1em; - text-align: center; - max-width: 21em; - margin: 2% auto 1em auto; - padding: 1.5em; -} -.messages.danger { background: #c0392b; } -.messages.warning { background: #e67e22; } -.messages.success { background: #27ae60; } -.messages.info { background: #2980b9; } - -.logged .messages { - max-width: none; - margin: 2% 5%; - padding: 1.5em 15%; -} - - -/* Fonts & Colors */ - -a { text-decoration: none; } - -h1, -h2, -h3, -h4, -h5, -h6 { - font-family: 'source_sans_probold'; - font-weight: normal; -} - -/* headings */ -h1, -.h1 { - font-size: 2.5em; -} - -h2, -.h2 { - font-size: 1.8em; -} - -.cwhite { - color: #fff; -} - -select, -.form-text, -textarea { - border: 0; - font-family: 'source_sans_proregular'; -} - - -/* Icons */ - -[class^="icon-"]:before, [class*=" icon-"]:before { - font-family: 'ynh_ssowat'; - font-size: 1em; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - margin-right: 0.5em; - - /* Better Font Rendering =========== */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.icon-user:before { content: '\e801'; } -.icon-lock:before { content: '\e800'; } -.icon-connexion:before { content: '\e802'; } -.icon-pencil:before { content: '\e804'; } -.icon-trash:before { content: '\e80c'; } -.icon-angle-left:before { content: '\e803'; } - - -/* ========================================================================== - 2 = Apps - ========================================================================== */ - -.apps { margin: 4% 5%; } - -.listing-apps { - margin: 0; - padding: 0; - letter-spacing: -5px; /*fix bug ff PC*/ - font-family: 'source_sans_probold'; -} - -.listing-apps li { - display: inline-block; - vertical-align: top; - letter-spacing: normal; - list-style: none; - margin: 0 0 1em 1em; - box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.4), - -2px -2px 3px 0 rgba(0, 0, 0, 0.7) inset; -} - -.listing-apps a { - display: block; - position: relative; - padding: 0.2em; - top: 0; - left: 0; - width: 2.7em; - height: 2.7em; - background: #666; - color: #fff; - font-size: 4em; - transition: all 0.3s ease; - -webkit-transition: all 0.3s ease; -} - .listing-apps a:hover, - .listing-apps a:focus { - left: -10px; - top: -10px; - box-shadow: none; - } - .listing-apps a:hover:before, - .listing-apps a:focus:before { - height: 10px; - } - .listing-apps a:hover:after, - .listing-apps a:focus:after { - width: 10px; - } - - .listing-apps a:hover:after, - .listing-apps a:focus:after, - .listing-apps a:hover:before, - .listing-apps a:focus:before { - background: #333; - } - - .listing-apps a:after, - .listing-apps a:before { - content: ""; - position: absolute; - transition: all 0.3s ease; - -webkit-transition: all 0.3s ease; - } - - .listing-apps a:before { - width: 100%; - height: 0; - left: 5px; - top: 100%; - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.4); - transform: skew(45deg, 0deg); - -webkit-transform: skew(45deg, 0deg); - } - - .listing-apps a:after { - width: 0; - height: 100%; - left: 100%; - top: 5px; - box-shadow: 5px 0 10px rgba(0, 0, 0, 0.4); - transform: skew(0deg, 45deg); - -webkit-transform: skew(0deg, 45deg); - } - - .listing-apps span { - display: block; - margin: -1.2em 0 0 0.2em; - } - .listing-apps .first-letter { - margin: 0; - display: inline-block; - } - .listing-apps .name { - font-family: 'source_sans_proregular'; - font-size: 0.3em; - } - -@media screen and (max-width: 450px) { - .apps {margin: 10% 5%;} - .listing-apps a {font-size: 3em;} - .listing-apps span + span { font-size: 0.32em; } - .listing-apps a:hover, - .listing-apps a:focus {left: -5px;top: -5px;} - .listing-apps a:hover:before, - .listing-apps a:focus:before {height: 5px;} - .listing-apps a:hover:after, - .listing-apps a:focus:after {width: 5px;} - .listing-apps a:before {left: 3px;box-shadow: 0 3px 5px rgba(0, 0, 0, 0.4);} - .listing-apps a:after {top: 3px;box-shadow: 3px 0 5px rgba(0, 0, 0, 0.4);} -} -@media screen and (max-width: 350px) { - .listing-apps a { - width: 2.5em; - height: 2.5em; - font-size: 2.8em; - } -} - - -/* ========================================================================== - 3 = User - ========================================================================== */ - -.user-container { - display:block; - position: relative; - max-width: 320px; - padding: 0.4em 1em; - color: #fff; -} - -.user-container-edit:after, -.user-container-password:after { - content: '\e803'; - font-family: 'ynh_ssowat'; - display: block; - height: 1em; - width: 1em; - position: absolute; - top: 50%; - left: -16px; - z-index: 0; - margin-top: -0.75em; - font-size: 2em; - font-weight: normal; - color: #b4b4b4; - opacity: 0; - transition: all 0.1s ease; - -webkit-transition: all 0.1s ease; -} -.user-container-edit:hover:after, -.user-container-password:hover:after { - left: -20px; - opacity: 1; -} -@media screen and (max-width: 480px) { - .user-container-edit:after, - .user-container-password:after {left: -10px;} - .user-container-edit:hover:after, - .user-container-password:hover:after {left: -14px;} -} - -.user-container:before { - display: block; - position: relative; - z-index: 1; - float: left; - margin-right: 10px; - content: '\e801'; - display: block; - font-family: 'ynh_ssowat'; - font-size: 4em; - text-align: center; - border: 3px solid #fff; - width: 1em; - height: 1em; - border-radius: 90px; - background: #b4b4b4; - color: #dedede; - overflow: hidden; - transition: all 0.1s ease; - -webkit-transition: all 0.1s ease; -} -.user-container:hover:before { - color: #fff; -} - -.user-container .user-username { - font-size: 1.5em; - margin: 0; -} - -.user-container .user-fullname { - font-size: 1em; - font-family: 'source_sans_proregular'; - display: block; - margin-top: -0.6em; -} - - -.user-container-info .user-username:after { - content: '\e804'; - font-family: 'ynh_ssowat'; - color: #b4b4b4; - display: inline-block;vertical-align: text-top; - font-size: 0.8em; - width: 1em; - height: 1em; - margin-left: .5em; - opacity: 0; - transition: all 0.1s ease; - -webkit-transition: all 0.1s ease; -} -.user-container-info:hover .user-username:after {opacity: 1;} - -.user-container .user-mail { - color: #999; - font-size: 0.9em; - display: block; - margin-top: -0.2em; -} - -/* User menu */ -.user-menu { - float: right; - margin: 0; - padding: 0; -} - .user-menu li { - list-style: none; - } - .user-menu a { - color: #999; - display: block; - padding: 1.25em 1em; - position: relative; - z-index: 1; - transition: all 0.1s ease; - -webkit-transition: all 0.1s ease; - } - .user-menu a:hover, - .user-menu a:focus { - color: #fff; - } - -@media screen and (max-width: 480px) { - .user-menu { - float: none; - } -} - - - -/* ========================================================================== - 4 = Forms - ========================================================================== */ - -button, -input, -select, -textarea { - font-family: inherit; - font-size: 100%; - margin: 0; -} - -input[type="search"] { - -webkit-appearance: textfield; -} -[type="submit"], -[type="password"], -[type="email"], -[type="text"] { - /* <3 Apple */ - -webkit-appearance: none; - -webkit-border-radius:0; -} - -input::-moz-focus-inner { - border: 0; - padding: 0; -} - -@media screen and (-webkit-min-device-pixel-ratio:0){ - select{ - -webkit-appearance: none; - border-radius: 0; - } -} - -.form-text { - padding: 0.8em; - width: 100%; -} - - -.form-section { - display: inline-block; - vertical-align: top; - width: 47%; -} -.form-section + .form-section {margin-left: 5%;} - -@media screen and (max-width: 768px) { - .form-section {width: 100%;} - .form-section + .form-section {margin-left: 0;} -} - -label { - display: inline-block; - padding: 0.3em 1em; - background: #30333b; - color: #fff; - font-size: 1.2em; - margin-top: 1em; - font-family: 'source_sans_probold'; - font-weight: normal; -} -label {cursor: pointer;} - -label + .help-link { - display: inline-block; - padding: 0.3em 1em; - font-size: 1.2em; - background: #41444f; - color: #fff; - font-weight: bold; - transition: all 0.1s ease; - -webkit-transition: all 0.1s ease; -} -label + .help-link:hover {background: #30333b;} - -.form-group { - background: none; - margin-bottom: 2em; -} - -.form-text { - border: 0; - background: #797b83; - color: #fff; - padding: 0.8em; - margin-bottom: 0.3em; - display: block; - position: relative;z-index: 1; /* prevent strange label overlap */ - transition: all 0.1s ease; - -webkit-transition: all 0.1s ease; -} -.form-test:-moz-placeholder{color:#ccc;} -.form-text::-moz-placeholder{color:#ccc;} -.form-text:-ms-input-placeholder{color:#ccc;} -.form-text::-webkit-input-placeholder{color:#ccc;} -:empty:invalid {box-shadow: none;} - -.form-text:last-child {margin-bottom:0;} - -.form-text:hover, -.form-text:focus { - background-color: #5d5f68; -} - -.form-text:disabled { color: #ccc; } -.form-text:disabled:hover {background-color:rgba(255, 255, 255, 0.3);} -input:disabled { - cursor: not-allowed; -} - -@media screen and (max-width: 480px) { - label, - label + .help-link {padding: 0.3em 0.8em;} - .form-text {padding: 0.8em;} - .form-group .btn {padding: 0.5em 0.8em;} -} - - - -/* Buttons */ -.btn { - background: #999; - display: inline-block; - padding: 0.5em 1em; - line-height: normal; - text-decoration: none; - color: #FFF; - cursor: pointer; - transition: all 0.1s ease; - -webkit-transition: all 0.1s ease; -} - -.large-btn { - padding: 0.8em 1.5em; - font-size: 1.1em; -} - -button.btn, -input.btn { - border:0; - cursor:pointer; -} - - .btn:hover, - .btn:focus { - background: #AAA; - } - -.important-btn { background: #c0392b;} - .important-btn:hover, - .important-btn:focus {background: #e74c3c;} - -.validate-btn { background: #27ae60;} - .validate-btn:hover, - .validate-btn:focus {background: #2ecc71;} - -.warning-btn { background: #e67e22;} - .warning-btn:hover, - .warning-btn:focus {background: #f39c12;} - -.classic-btn { background: #2980b9;} - .classic-btn:hover, - .classic-btn:focus {background: #3498db;} - -.link-btn { background: none;} - .link-btn:hover, - .link-btn:focus {background: #41444f;text-decoration: underline;} - - -.btn-group { - margin: 4em 0; - text-align: right; -} - -/* Login form */ - -.login-form { - max-width: 21em; - margin: 0 auto; -} - -.login-form .btn { - width: 100%; - padding: 0.8em 1em; -} - -.login-form .form-group { - position: relative; - margin-bottom: 1em; - background: #fff; -} - - .login-form label { - display: block; - min-width: 1em; - margin: 0; - padding: 0; - font-size: 1em; - } - - .login-form label:before { - background: #eee; - color: #666; - position: absolute; - z-index: 2; - top: 0; - left: 0; - width: 2.5em; - height: 100%; - line-height: 3em; - text-align: center; - } - - .login-form .form-text { - padding: 0.8em 0.8em 0.8em 3em; - width: 100%; - background: #fff; - color: #41444f; - } - .login-form .form-test:-moz-placeholder{color:#999;} - .login-form .form-text::-moz-placeholder{color:#999;} - .login-form .form-text:-ms-input-placeholder{color:#999;} - .login-form .form-text::-webkit-input-placeholder{color:#999;} - - -/* Edit form*/ - -.form-edit .form-group .btn:before { - content:"+"; - display: inline-block; - padding-right: 0.75em; - font-weight: bold; -} - - -@media screen and (min-width: 768px) { - .form-edit .btn-group { - float: right; - } - .form-edit .btn-group + .btn-group { - float: left; - } -} - - -/* ========================================================================== - 5 = Footer - ========================================================================== */ - -.footer { - display: inline-block; - width: auto; -} - .footer nav { - margin: 0 1em; - padding: 0.25em; - border-top: 1px solid #666; - font-size: 0.9em; - } - - .footer a { - display: inline-block; - vertical-align: top; - color: #999; - } - .footer a:before { - content: "•"; - display: inline-block; - vertical-align: top; - padding: 0 0.5em 0 0.25em; - color: #666; - } - .footer a:first-child:before {content: none;} - - .footer a:hover, - .footer a:active { - color: #fff; - text-decoration: none; - } - -@media screen and (max-width: 480px) { - .footer a { - display: block; - } - .footer a:before { - content: none; - } -} - -/* ========================================================================== - Internet Explorer - ========================================================================== */ - -/*IE8 and IE9*/ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -nav, -section, -summary { - display: block; -} diff --git a/portal/assets/fonts/sourcesanspro-bold-webfont.eot b/portal/assets/fonts/sourcesanspro-bold-webfont.eot deleted file mode 100755 index bbbe8ee..0000000 Binary files a/portal/assets/fonts/sourcesanspro-bold-webfont.eot and /dev/null differ diff --git a/portal/assets/fonts/sourcesanspro-bold-webfont.ttf b/portal/assets/fonts/sourcesanspro-bold-webfont.ttf deleted file mode 100755 index 8ced94f..0000000 Binary files a/portal/assets/fonts/sourcesanspro-bold-webfont.ttf and /dev/null differ diff --git a/portal/assets/fonts/sourcesanspro-regular-webfont.eot b/portal/assets/fonts/sourcesanspro-regular-webfont.eot deleted file mode 100755 index 850509c..0000000 Binary files a/portal/assets/fonts/sourcesanspro-regular-webfont.eot and /dev/null differ diff --git a/portal/assets/fonts/sourcesanspro-regular-webfont.ttf b/portal/assets/fonts/sourcesanspro-regular-webfont.ttf deleted file mode 100755 index b9d2b7a..0000000 Binary files a/portal/assets/fonts/sourcesanspro-regular-webfont.ttf and /dev/null differ diff --git a/portal/assets/fonts/ynh_ssowat/ynh_ssowat.eot b/portal/assets/fonts/ynh_ssowat/ynh_ssowat.eot deleted file mode 100644 index 8377071..0000000 Binary files a/portal/assets/fonts/ynh_ssowat/ynh_ssowat.eot and /dev/null differ diff --git a/portal/assets/fonts/ynh_ssowat/ynh_ssowat.svg b/portal/assets/fonts/ynh_ssowat/ynh_ssowat.svg deleted file mode 100644 index c1ffa27..0000000 --- a/portal/assets/fonts/ynh_ssowat/ynh_ssowat.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - -Copyright (C) 2014 by original authors @ fontello.com - - - - - - - - - - - - - \ No newline at end of file diff --git a/portal/assets/fonts/ynh_ssowat/ynh_ssowat.ttf b/portal/assets/fonts/ynh_ssowat/ynh_ssowat.ttf deleted file mode 100644 index 60690e4..0000000 Binary files a/portal/assets/fonts/ynh_ssowat/ynh_ssowat.ttf and /dev/null differ diff --git a/portal/assets/fonts/ynh_ssowat/ynh_ssowat.woff b/portal/assets/fonts/ynh_ssowat/ynh_ssowat.woff deleted file mode 100644 index a7333a4..0000000 Binary files a/portal/assets/fonts/ynh_ssowat/ynh_ssowat.woff and /dev/null differ diff --git a/portal/assets/icons/apple-touch-icon-114x114.png b/portal/assets/icons/apple-touch-icon-114x114.png deleted file mode 100644 index 45d121c..0000000 Binary files a/portal/assets/icons/apple-touch-icon-114x114.png and /dev/null differ diff --git a/portal/assets/icons/apple-touch-icon-120x120.png b/portal/assets/icons/apple-touch-icon-120x120.png deleted file mode 100644 index fa23db1..0000000 Binary files a/portal/assets/icons/apple-touch-icon-120x120.png and /dev/null differ diff --git a/portal/assets/icons/apple-touch-icon-144x144.png b/portal/assets/icons/apple-touch-icon-144x144.png deleted file mode 100644 index 16f32b3..0000000 Binary files a/portal/assets/icons/apple-touch-icon-144x144.png and /dev/null differ diff --git a/portal/assets/icons/apple-touch-icon-152x152.png b/portal/assets/icons/apple-touch-icon-152x152.png deleted file mode 100644 index f3c0294..0000000 Binary files a/portal/assets/icons/apple-touch-icon-152x152.png and /dev/null differ diff --git a/portal/assets/icons/apple-touch-icon-57x57.png b/portal/assets/icons/apple-touch-icon-57x57.png deleted file mode 100644 index ee9f56a..0000000 Binary files a/portal/assets/icons/apple-touch-icon-57x57.png and /dev/null differ diff --git a/portal/assets/icons/apple-touch-icon-60x60.png b/portal/assets/icons/apple-touch-icon-60x60.png deleted file mode 100644 index a41fe8c..0000000 Binary files a/portal/assets/icons/apple-touch-icon-60x60.png and /dev/null differ diff --git a/portal/assets/icons/apple-touch-icon-72x72.png b/portal/assets/icons/apple-touch-icon-72x72.png deleted file mode 100644 index fd6b339..0000000 Binary files a/portal/assets/icons/apple-touch-icon-72x72.png and /dev/null differ diff --git a/portal/assets/icons/apple-touch-icon-76x76.png b/portal/assets/icons/apple-touch-icon-76x76.png deleted file mode 100644 index 4303c46..0000000 Binary files a/portal/assets/icons/apple-touch-icon-76x76.png and /dev/null differ diff --git a/portal/assets/icons/apple-touch-icon-precomposed.png b/portal/assets/icons/apple-touch-icon-precomposed.png deleted file mode 100644 index a5ffdad..0000000 Binary files a/portal/assets/icons/apple-touch-icon-precomposed.png and /dev/null differ diff --git a/portal/assets/icons/apple-touch-icon.png b/portal/assets/icons/apple-touch-icon.png deleted file mode 100644 index f3c0294..0000000 Binary files a/portal/assets/icons/apple-touch-icon.png and /dev/null differ diff --git a/portal/assets/icons/browserconfig.xml b/portal/assets/icons/browserconfig.xml deleted file mode 100644 index fe44cae..0000000 --- a/portal/assets/icons/browserconfig.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - #da532c - - - diff --git a/portal/assets/icons/favicon-160x160.png b/portal/assets/icons/favicon-160x160.png deleted file mode 100644 index dcfd55a..0000000 Binary files a/portal/assets/icons/favicon-160x160.png and /dev/null differ diff --git a/portal/assets/icons/favicon-16x16.png b/portal/assets/icons/favicon-16x16.png deleted file mode 100644 index b31f28d..0000000 Binary files a/portal/assets/icons/favicon-16x16.png and /dev/null differ diff --git a/portal/assets/icons/favicon-196x196.png b/portal/assets/icons/favicon-196x196.png deleted file mode 100644 index 7f3ff17..0000000 Binary files a/portal/assets/icons/favicon-196x196.png and /dev/null differ diff --git a/portal/assets/icons/favicon-32x32.png b/portal/assets/icons/favicon-32x32.png deleted file mode 100644 index 6f0bc53..0000000 Binary files a/portal/assets/icons/favicon-32x32.png and /dev/null differ diff --git a/portal/assets/icons/favicon-96x96.png b/portal/assets/icons/favicon-96x96.png deleted file mode 100644 index ba8da95..0000000 Binary files a/portal/assets/icons/favicon-96x96.png and /dev/null differ diff --git a/portal/assets/icons/favicon.ico b/portal/assets/icons/favicon.ico deleted file mode 100644 index 5d09b3d..0000000 Binary files a/portal/assets/icons/favicon.ico and /dev/null differ diff --git a/portal/assets/icons/mstile-144x144.png b/portal/assets/icons/mstile-144x144.png deleted file mode 100644 index 4dfa436..0000000 Binary files a/portal/assets/icons/mstile-144x144.png and /dev/null differ diff --git a/portal/assets/icons/mstile-150x150.png b/portal/assets/icons/mstile-150x150.png deleted file mode 100644 index ac459a2..0000000 Binary files a/portal/assets/icons/mstile-150x150.png and /dev/null differ diff --git a/portal/assets/icons/mstile-310x150.png b/portal/assets/icons/mstile-310x150.png deleted file mode 100644 index 564b149..0000000 Binary files a/portal/assets/icons/mstile-310x150.png and /dev/null differ diff --git a/portal/assets/icons/mstile-310x310.png b/portal/assets/icons/mstile-310x310.png deleted file mode 100644 index b374084..0000000 Binary files a/portal/assets/icons/mstile-310x310.png and /dev/null differ diff --git a/portal/assets/icons/mstile-70x70.png b/portal/assets/icons/mstile-70x70.png deleted file mode 100644 index fbc5b93..0000000 Binary files a/portal/assets/icons/mstile-70x70.png and /dev/null differ diff --git a/portal/assets/img/logo-ynh-white.svg b/portal/assets/img/logo-ynh-white.svg deleted file mode 100644 index f960fd3..0000000 --- a/portal/assets/img/logo-ynh-white.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - -]> - - - - - - - - - - - diff --git a/portal/assets/img/logo-ynh.svg b/portal/assets/img/logo-ynh.svg deleted file mode 100644 index 8f2a56e..0000000 --- a/portal/assets/img/logo-ynh.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - -]> - - - - - - - - - - - - - - - - - diff --git a/portal/assets/js/ynh_portal.js b/portal/assets/js/ynh_portal.js deleted file mode 100644 index e018167..0000000 --- a/portal/assets/js/ynh_portal.js +++ /dev/null @@ -1,401 +0,0 @@ -/* -=============================================================================== - This JS file is loaded : - - in the YunoHost user portal - - on every app page if the app nginx's conf does include the ynh snippet -=============================================================================== -*/ - -/* -===================== - Utilities -===================== -*/ - -/* Console log fix */ -if (typeof(console) === 'undefined') { - var console = {}; - console.log = console.error = console.info = console.debug = console.warn = console.trace = console.dir = console.dirxml = console.group = console.groupEnd = console.time = console.timeEnd = console.assert = console.profile = function() {}; -} - -/* Cookies utilities */ -function setCookie(cName, cValue, expDays) { - let date = new Date(); - date.setTime(date.getTime() + (expDays * 24 * 60 * 60 * 1000)); - const expires = "expires=" + date.toUTCString(); - document.cookie = cName + "=" + cValue + "; " + expires + "; path=/"; -} -function getCookie(cName) { - const name = cName + "="; - const cDecoded = decodeURIComponent(document.cookie); //to be careful - const cArr = cDecoded .split('; '); - let res; - cArr.forEach(val => { - if (val.indexOf(name) === 0) res = val.substring(name.length); - }) - return res; -} - -/* Array utilities - https://github.com/Darklg/JavaScriptUtilities/blob/master/assets/js/vanilla-js/libs/vanilla-arrays.js --------------------------- */ -Array.contains = function(needle, haystack) { - var i = 0, - length = haystack.length; - - for (; i < length; i++) { - if (haystack[i] === needle) return true; - } - return false; -}; -Array.each = function(arrayToParse, callback) { - var i = 0, - length = arrayToParse.length; - for (; i < length; i++) { - callback(arrayToParse[i]); - } -}; - - - -/* CSS classes utilities - https://github.com/Darklg/JavaScriptUtilities/blob/master/assets/js/vanilla-js/libs/vanilla-classes.js --------------------------- */ -Element.getClassNames = function(element) { - var classNames = [], - elementClassName = element.className; - if (elementClassName !== '') { - elementClassName = elementClassName.replace(/\s+/g, ' '); - classNames = elementClassName.split(' '); - } - return classNames; -}; -Element.hasClass = function(element, className) { - if (element.classList) { - return element.classList.contains(className); - } - return Array.contains(className, Element.getClassNames(element)); -}; -Element.addClass = function(element, className) { - if (element.classList) { - element.classList.add(className); - return; - } - if (!Element.hasClass(element, className)) { - var elementClasses = Element.getClassNames(element); - elementClasses.push(className); - element.className = elementClasses.join(' '); - } -}; -Element.removeClass = function(element, className) { - if (element.classList) { - element.classList.remove(className); - return; - } - var elementClasses = Element.getClassNames(element); - var newElementClasses = []; - var i = 0, - arLength = elementClasses.length; - for (; i < arLength; i++) { - if (elementClasses[i] !== className) { - newElementClasses.push(elementClasses[i]); - } - } - element.className = newElementClasses.join(' '); -}; -Element.toggleClass = function(element, className) { - if (!Element.hasClass(element, className)) { - Element.addClass(element, className); - } - else { - Element.removeClass(element, className); - } -}; - - -/* Add Event - https://github.com/Darklg/JavaScriptUtilities/blob/master/assets/js/vanilla-js/libs/vanilla-events.js --------------------------- */ -window.addEvent = function(el, eventName, callback, options) { - if (el == null) { return; } - if (el.addEventListener) { - if (!options || typeof(options) !== "object") { - options = {}; - } - - options.capture = false; - el.addEventListener(eventName, callback, options); - } - else if (el.attachEvent) { - el.attachEvent("on" + eventName, function(e) { - return callback.call(el, e); - }); - } -}; -window.eventPreventDefault = function(event) { - return (event.preventDefault) ? event.preventDefault() : event.returnValue = false; -}; - - -/* Draggable - - Sources : - http://jsfiddle.net/5t3Ju/ - http://stackoverflow.com/questions/9334084/moveable-draggable-div - http://jsfiddle.net/tovic/Xcb8d/light/ --------------------------- */ - -function make_element_draggable(id) { - - // Variables - this.elem = document.getElementById(id), - this.selected = null, // Selected element - this.dragged = false, // Dragging status - this.x_pos = 0, this.y_pos = 0, // Stores x & y coordinates of the mouse pointer - this.x_elem = 0, this.y_elem = 0; // Stores top, left values (edge) of the element - - var _initDrag = function(e){ - if (e.type === "touchstart"){ - x_pos = e.touches[0].clientX; - y_pos = e.touches[0].clientY; - } - - selected = elem; - x_elem = x_pos - selected.offsetLeft; - y_elem = y_pos - selected.offsetTop; - - // We add listening event for the iframe itself ... - // otherwise dragging the tile on the iframe doesn't - // work properly. - // We do this at click time to have a better chance - // that the iframe's body is indeed loaded ... - // (a bit hackish but meh) - portalOverlay = document.getElementById("ynh-overlay").contentDocument.body; - window.addEvent(portalOverlay, 'mousemove', _onMove); - window.addEvent(portalOverlay, 'touchmove', _onMove, {passive: false}); - }; - - var _shutDrag = function(e){ - selected = null; - }; - - var _onMove = function(e){ - // Get position - x_pos = document.all ? window.event: e.pageX; - y_pos = document.all ? window.event : e.pageY; - - if (e.type === "touchmove") { - x_pos = e.touches[0].clientX; - y_pos = e.touches[0].clientY; - } - - if (selected !== null) { - if (e.type === "touchmove"){ - event.preventDefault(); - } - dragged = true; - selected.style.left = (x_pos - x_elem) + 'px'; - selected.style.top = (y_pos - y_elem) + 'px'; - // Store positions in cookies - setCookie('ynh_overlay_top', selected.style.top, 30); - setCookie('ynh_overlay_left', selected.style.left, 30); - } - }; - - // Prevent native D'n'D behavior - window.addEvent(elem, 'dragstart', function(e){ - window.eventPreventDefault(e); - }); - - // Start dragging - window.addEvent(elem, 'mousedown', _initDrag); - window.addEvent(elem, 'touchstart', _initDrag); - - // Will be called when user dragging an element - window.addEvent(window, 'mousemove', _onMove); - window.addEvent(window, 'touchmove', _onMove, {passive: false}); - - // Destroy the object when we are done - window.addEvent(window, 'mouseup', _shutDrag); - window.addEvent(window, 'touchend', _shutDrag); - window.addEvent(window, 'touchcancel', _shutDrag); - - // Handle click event - window.addEvent(elem, 'click', function(e){ - // Prevent default event - window.eventPreventDefault(e); - - // Do not propagate to other click event if dragged out - if (dragged) { - e.stopImmediatePropagation(); - } - // Reset dragging status - dragged = false; - }); -}; - -/* ---------------------------------------------------------- - Main ----------------------------------------------------------- */ -window.addEvent(document, 'DOMContentLoaded', function() { - - // 3 different cases : - // - this script is loaded from inside an app - // - this script is loaded inside the portal, inside an iframe/overlay activated by clicking the portal button inside an app - // - this script is loaded inside the "regular" portal when going to /yunohost/sso. - - var in_app = ! document.body.classList.contains('ynh-user-portal'); - var in_overlay_iframe = (window.location != window.parent.location); - - if (in_app) - { - // Do not load inside an app iframe (Roundcube visualisation panel for example). - if (window.frameElement == null) { - init_portal_button_and_overlay(); - } - } - else - { - init_portal(); - if (in_overlay_iframe) { tweak_portal_when_in_iframe(); } - } -}); - -// -// This function is called when ynh_portal.js is included in an app -// -// It will create the small yunohost "portal button" usually in the bottom -// right corner and initialize the portal overlay, shown when clicking the -// portal button meant to make it easier to switch between apps. -// -function init_portal_button_and_overlay() -{ - // Set and store meta viewport - var meta_viewport = document.querySelector('meta[name="viewport"]'); - if (meta_viewport === null) { - meta_viewport = document.createElement('meta'); - meta_viewport.setAttribute('name', "viewport"); - meta_viewport.setAttribute('content', ""); - document.getElementsByTagName('head')[0].insertBefore(meta_viewport, null); - } - meta_viewport = document.querySelector('meta[name="viewport"]'); - meta_viewport_content = meta_viewport.getAttribute('content'); - - // Prepare and inject the portal overlay (what is activated when clicking on the portal button) - var portalOverlay = document.createElement('iframe'); - portalOverlay.src = "/yunohost/sso/portal.html"; - portalOverlay.setAttribute("id","ynh-overlay"); - portalOverlay.setAttribute("style","display: none;"); // make sure the overlay is invisible already when loading it - // portalOverlay.setAttribute("class","ynh-fadeOut"); // set overlay as masked when loading it - document.body.insertBefore(portalOverlay, null); - - // Inject portal button - var portalButton = document.createElement('a'); - portalButton.setAttribute('id', 'ynh-overlay-switch'); - portalButton.setAttribute('href', '/yunohost/sso/'); - portalButton.setAttribute('class', 'disableAjax'); - // Checks if cookies exist and apply positioning - if (getCookie('ynh_overlay_top') != null && getCookie('ynh_overlay_left') != null) { - portalButton.style.top = getCookie('ynh_overlay_top'); - portalButton.style.left = getCookie('ynh_overlay_left'); - } - document.body.insertBefore(portalButton, null); - // Make portal button draggable, for user convenience - make_element_draggable('ynh-overlay-switch'); - - // Bind portal button - window.addEvent(portalButton, 'click', function(e){ - // Prevent default click - window.eventPreventDefault(e); - // Toggle overlay on YNHPortal button click - Element.toggleClass(document.querySelector('html'), 'ynh-panel-active'); - Element.toggleClass(portalOverlay, 'ynh-active'); - - if (Element.hasClass(portalOverlay, 'ynh-active')) { - portalOverlay.setAttribute("style","display: block;"); - meta_viewport.setAttribute('content', meta_viewport_content); - Element.addClass(portalOverlay, 'ynh-fadeIn'); - Element.removeClass(portalOverlay, 'ynh-fadeOut'); - } else { - portalOverlay.setAttribute("style","display: none;"); - meta_viewport.setAttribute('content', "width=device-width"); - Element.removeClass(portalOverlay, 'ynh-fadeIn'); - Element.addClass(portalOverlay, 'ynh-fadeOut'); - } - }); -} - -// -// This function is called to initialize elements like the app tile colors and other things ... -// -function init_portal() -{ - - window.addEvent(document.getElementById('add-mailalias'), "click", function() { - // Clone last input. - var inputAliasClone = document.querySelector('.mailalias-input').cloneNode(true); - // Empty value. - inputAliasClone.value = ''; - // Append to form-group. - this.parentNode.insertBefore(inputAliasClone, this); - }); - - window.addEvent(document.getElementById('add-maildrop'), "click", function() { - // Clone last input. - var inputDropClone = document.querySelector('.maildrop-input').cloneNode(true); - // Empty value. - inputDropClone.value = ''; - // Append to form-group. - this.parentNode.insertBefore(inputDropClone, this); - }); - - Array.each(document.getElementsByClassName("app-tile"), function(el) { - // Set first-letter data attribute. - el.querySelector('.first-letter').innerHTML = el.getAttribute("data-appname").substring(0, 2); - // handle app links so they work both in plain info page and in the info iframe called from ynh_portal.js - window.addEvent(el, 'click', function(event) { - // if asked to open in new tab - if (event.ctrlKey || event.shiftKey || event.metaKey - || (event.button && event.button == 1)) { - return - } - // if asked in current tab - else { - event.preventDefault(); - parent.location.href=this.href; - return false; - }; - }); - }); -} - - -function tweak_portal_when_in_iframe() -{ - // Set class to body to show we're in overlay - document.body.classList.add('in_app_overlay'); - let userContainer = document.querySelector('a.user-container'); - if (userContainer) { - userContainer.classList.replace('user-container-info', 'user-container-edit'); - userContainer.setAttribute('href', userContainer - .getAttribute('href') - .replace('edit.html', '')); - window.addEvent(userContainer, 'click', function(e) { - e.preventDefault(); - e.stopPropagation(); - window.parent.location.href = userContainer.getAttribute('href'); - }); - } - let logoutButton = document.getElementById('ynh-logout'); - if (logoutButton) - { - // We force to do the logout "globally", not just in the - // iframe, otherwise after login out the url might still be - // domain.tld/app which is weird ... - window.addEvent(logoutButton, 'click', function(e) { - e.preventDefault(); - e.stopPropagation(); - window.parent.location.href = logoutButton.getAttribute("href"); - }); - } -} \ No newline at end of file diff --git a/portal/assets/themes/clouds/background.jpg b/portal/assets/themes/clouds/background.jpg deleted file mode 100644 index 32a876c..0000000 Binary files a/portal/assets/themes/clouds/background.jpg and /dev/null differ diff --git a/portal/assets/themes/clouds/cloud.png b/portal/assets/themes/clouds/cloud.png deleted file mode 100644 index 1909064..0000000 Binary files a/portal/assets/themes/clouds/cloud.png and /dev/null differ diff --git a/portal/assets/themes/clouds/custom_overlay.css b/portal/assets/themes/clouds/custom_overlay.css deleted file mode 100644 index 7f1a000..0000000 --- a/portal/assets/themes/clouds/custom_overlay.css +++ /dev/null @@ -1,17 +0,0 @@ -/* -=============================================================================== - This file may contain extra CSS rules loaded on all apps page (*if* the app - nginx's conf does include the appropriate snippet) for the small YunoHost - button in bottom-right corner + portal overlay. - - The yunohost button corresponds to : #ynh-overlay-switch - The yunohost portal overlay / iframe corresponds to : #ynh-overlay - - BE CAREFUL that you should *not* add too-general rules that apply to - non-yunohost elements (for instance all 'a' or 'p' elements...) as it will - likely break app's rendering -=============================================================================== -*/ -#ynh-overlay-switch { - background-image: url("./cloud.png"); -} diff --git a/portal/assets/themes/clouds/custom_portal.css b/portal/assets/themes/clouds/custom_portal.css deleted file mode 100644 index 2591ca2..0000000 --- a/portal/assets/themes/clouds/custom_portal.css +++ /dev/null @@ -1,43 +0,0 @@ -/* -=============================================================================== - This file contain extra CSS rules to customize the YunoHost user portal and - can be used to customize app tiles, buttons, etc... -=============================================================================== -*/ - -/* Make page texts black */ -.user-container h2, -.user-container small, -.user-container .user-mail, -.user-container .user-mail, -.content .footer a, -a.app-tile, -#ynh-logout { - color: black !important; -} - -.ynh-user-portal { - background-image: url("background.jpg"); - background-repeat: no-repeat; - background-size: cover; - width: 100%; - height: 100%; -} - -/* Apps colors */ -.app-tile { - background-color: rgba(255, 255, 255, 0.5) !important; -} - -.app-tile:hover:after, -.app-tile:focus:after, -.app-tile:hover:before, -.app-tile:focus:before { - background: rgba(255, 255, 255, 0.5) !important; -} - -/* Use a custom logo image */ -#ynh-logo { - z-index: 10; - background-image: url("./cloud.png"); -} diff --git a/portal/assets/themes/clouds/custom_portal.js b/portal/assets/themes/clouds/custom_portal.js deleted file mode 100644 index 80c27bd..0000000 --- a/portal/assets/themes/clouds/custom_portal.js +++ /dev/null @@ -1,33 +0,0 @@ -/* -=============================================================================== - This JS file may be used to customize the YunoHost user portal *and* also - will be loaded in all app pages if the app nginx's conf does include the - appropriate snippet. - - You can monkeypatch init_portal (loading of the user portal) and - init_portal_button_and_overlay (loading of the button and overlay...) to do - custom stuff -=============================================================================== -*/ - -/* - * Monkeypatch init_portal to customize the app tile style - * -init_portal_original = init_portal; -init_portal = function() -{ - init_portal_original(); - // Some stuff here -} -*/ - -/* - * Monkey patching example to do custom stuff when loading inside an app - * -init_portal_button_and_overlay_original = init_portal_button_and_overlay; -init_portal_button_and_overlay = function() -{ - init_portal_button_and_overlay_original(); - // Custom stuff to do when loading inside an app -} -*/ diff --git a/portal/assets/themes/default/custom_overlay.css b/portal/assets/themes/default/custom_overlay.css deleted file mode 100644 index 0074f3e..0000000 --- a/portal/assets/themes/default/custom_overlay.css +++ /dev/null @@ -1,14 +0,0 @@ -/* -=============================================================================== - This file may contain extra CSS rules loaded on all apps page (*if* the app - nginx's conf does include the appropriate snippet) for the small YunoHost - button in bottom-right corner + portal overlay. - - The yunohost button corresponds to : #ynh-overlay-switch - The yunohost portal overlay / iframe corresponds to : #ynh-overlay - - BE CAREFUL that you should *not* add too-general rules that apply to - non-yunohost elements (for instance all 'a' or 'p' elements...) as it will - likely break app's rendering -=============================================================================== -*/ diff --git a/portal/assets/themes/default/custom_portal.css b/portal/assets/themes/default/custom_portal.css deleted file mode 100644 index 7346398..0000000 --- a/portal/assets/themes/default/custom_portal.css +++ /dev/null @@ -1,145 +0,0 @@ -/* -=============================================================================== - This file contain extra CSS rules to customize the YunoHost user portal and - can be used to customize app tiles, buttons, etc... -=============================================================================== -*/ - -.bluebg { - background: #3498DB!important; -} -.bluebg:hover:after, -.bluebg:focus:after, -.bluebg:hover:before, -.bluebg:focus:before { - background: #16527A!important; -} - -.purplebg { - background: #9B59B6!important; -} -.purplebg:hover:after, -.purplebg:focus:after, -.purplebg:hover:before, -.purplebg:focus:before { - background: #532C64!important; -} - -.redbg { - background: #E74C3C!important; -} -.redbg:hover:after, -.redbg:focus:after, -.redbg:hover:before, -.redbg:focus:before { - background: #921E12!important; -} - -.orangebg { - background: #F39C12!important; -} -.orangebg:hover:after, -.orangebg:focus:after, -.orangebg:hover:before, -.orangebg:focus:before { - background: #7F5006!important; -} - -.greenbg { - background: #2ECC71!important; -} -.greenbg:hover:after, -.greenbg:focus:after, -.greenbg:hover:before, -.greenbg:focus:before { - background: #176437!important; -} - -.darkbluebg { - background: #34495E!important; -} -.darkbluebg:hover:after, -.darkbluebg:focus:after, -.darkbluebg:hover:before, -.darkbluebg:focus:before { - background: #07090C!important; -} - -.lightbluebg { - background: #6A93D4!important; -} -.lightbluebg:hover:after, -.lightbluebg:focus:after, -.lightbluebg:hover:before, -.lightbluebg:focus:before { - background: #2B5394!important; -} - -.yellowbg { - background: #F1C40F!important; -} -.yellowbg:hover:after, -.yellowbg:focus:after, -.yellowbg:hover:before, -.yellowbg:focus:before { - background: #796307!important; -} - - -.lightpinkbg { - background: #F76F87!important; -} -.lightpinkbg:hover:after, -.lightpinkbg:focus:after, -.lightpinkbg:hover:before, -.lightpinkbg:focus:before { - background: #DA0C31!important; -} - -/* Following colors are not used yet */ -.pinkbg { - background: #D66D92!important; -} -.pinkbg:hover:after, -.pinkbg:focus:after, -.pinkbg:hover:before, -.pinkbg:focus:before { - background: #992B52!important; -} - -.turquoisebg { - background: #1ABC9C!important; -} -.turquoisebg:hover:after, -.turquoisebg:focus:after, -.turquoisebg:hover:before, -.turquoisebg:focus:before { - background: #0B4C3F!important; -} -.lightyellow { - background: #FFC973!important; -} -.lightyellow:hover:after, -.lightyellow:focus:after, -.lightyellow:hover:before, -.lightyellow:focus:before { - background: #F39500!important; -} -.lightgreen { - background: #B5F36D!important; -} -.lightgreen:hover:after, -.lightgreen:focus:after, -.lightgreen:hover:before, -.lightgreen:focus:before { - background: #77CF11!important; -} -.purpledarkbg { - background: #8E44AD!important; -} -.purpledarkbg:hover:after, -.purpledarkbg:focus:after, -.purpledarkbg:hover:before, -.purpledarkbg:focus:before { - background: #432051!important; -} diff --git a/portal/assets/themes/default/custom_portal.js b/portal/assets/themes/default/custom_portal.js deleted file mode 100644 index 7849e7c..0000000 --- a/portal/assets/themes/default/custom_portal.js +++ /dev/null @@ -1,40 +0,0 @@ -/* -=============================================================================== - This JS file may be used to customize the YunoHost user portal *and* also - will be loaded in all app pages if the app nginx's conf does include the - appropriate snippet. - - You can monkeypatch init_portal (loading of the user portal) and - init_portal_button_and_overlay (loading of the button and overlay...) to do - custom stuff -=============================================================================== -*/ - -var app_tile_colors = ['redbg','purpledarkbg','darkbluebg','orangebg','greenbg', 'yellowbg','lightpinkbg','pinkbg','turquoisebg','lightbluebg', 'bluebg']; - -function set_app_tile_style(el) -{ - // Select a color value from the App label - randomColorNumber = parseInt(el.textContent, 36) % app_tile_colors.length; - // Add color class. - el.classList.add(app_tile_colors[randomColorNumber]); -} - -// Monkeypatch init_portal to customize the app tile style -init_portal_original = init_portal; -init_portal = function() -{ - init_portal_original(); - Array.each(document.getElementsByClassName("app-tile"), set_app_tile_style); -} - -/* - * Monkey patching example to do custom stuff when loading inside an app - * -init_portal_button_and_overlay_original = init_portal_button_and_overlay; -init_portal_button_and_overlay = function() -{ - init_portal_button_and_overlay_original(); - // Custom stuff to do when loading inside an app -} -*/ diff --git a/portal/assets/themes/light/custom_overlay.css b/portal/assets/themes/light/custom_overlay.css deleted file mode 100644 index a2d0151..0000000 --- a/portal/assets/themes/light/custom_overlay.css +++ /dev/null @@ -1,26 +0,0 @@ -/* -=============================================================================== - This file may contain extra CSS rules loaded on all apps page (*if* the app - nginx's conf does include the appropriate snippet) for the small YunoHost - button in bottom-right corner + portal overlay. - - The yunohost button corresponds to : #ynh-overlay-switch - The yunohost portal overlay / iframe corresponds to : #ynh-overlay - - BE CAREFUL that you should *not* add too-general rules that apply to - non-yunohost elements (for instance all 'a' or 'p' elements...) as it will - likely break app's rendering -=============================================================================== -*/ - -#ynh-overlay-switch { - /* FIXME : idk if this is an issue or not to have /yunohost/sso hard-coded here */ - background-image: url("/yunohost/sso/assets/img/logo-ynh.svg"); - border-color: #eee; - background-color: #eee; -} - -#ynh-overlay-switch:hover { - border-color: #ccc; - background-color: #ccc; -} diff --git a/portal/assets/themes/light/custom_portal.css b/portal/assets/themes/light/custom_portal.css deleted file mode 100644 index 110ac2c..0000000 --- a/portal/assets/themes/light/custom_portal.css +++ /dev/null @@ -1,179 +0,0 @@ -/* -=============================================================================== - This file contain extra CSS rules to customize the YunoHost user portal and - can be used to customize app tiles, buttons, etc... -=============================================================================== -*/ - -body { - background: #fff; -} - -#ynh-logo { - background-image: url("../../img/logo-ynh.svg"); -} - -.login-form .form-group { - border: 1px solid #bbb; -} - -.user-container, -.user-menu a, -.link-btn, -.footer a { - color: #555; -} - -.user-menu a:hover, -.footer a:hover { - color: #000; -} - -.form-text:disabled:hover { - background: #797b83; -} - -.link-btn, -.link-btn:hover { - background: none; -} - - -.bluebg { - background: #3498DB!important; -} -.bluebg:hover:after, -.bluebg:focus:after, -.bluebg:hover:before, -.bluebg:focus:before { - background: #16527A!important; -} - -.purplebg { - background: #9B59B6!important; -} -.purplebg:hover:after, -.purplebg:focus:after, -.purplebg:hover:before, -.purplebg:focus:before { - background: #532C64!important; -} - -.redbg { - background: #E74C3C!important; -} -.redbg:hover:after, -.redbg:focus:after, -.redbg:hover:before, -.redbg:focus:before { - background: #921E12!important; -} - -.orangebg { - background: #F39C12!important; -} -.orangebg:hover:after, -.orangebg:focus:after, -.orangebg:hover:before, -.orangebg:focus:before { - background: #7F5006!important; -} - -.greenbg { - background: #2ECC71!important; -} -.greenbg:hover:after, -.greenbg:focus:after, -.greenbg:hover:before, -.greenbg:focus:before { - background: #176437!important; -} - -.darkbluebg { - background: #34495E!important; -} -.darkbluebg:hover:after, -.darkbluebg:focus:after, -.darkbluebg:hover:before, -.darkbluebg:focus:before { - background: #07090C!important; -} - -.lightbluebg { - background: #6A93D4!important; -} -.lightbluebg:hover:after, -.lightbluebg:focus:after, -.lightbluebg:hover:before, -.lightbluebg:focus:before { - background: #2B5394!important; -} - -.yellowbg { - background: #F1C40F!important; -} -.yellowbg:hover:after, -.yellowbg:focus:after, -.yellowbg:hover:before, -.yellowbg:focus:before { - background: #796307!important; -} - - -.lightpinkbg { - background: #F76F87!important; -} -.lightpinkbg:hover:after, -.lightpinkbg:focus:after, -.lightpinkbg:hover:before, -.lightpinkbg:focus:before { - background: #DA0C31!important; -} - -/* Following colors are not used yet */ -.pinkbg { - background: #D66D92!important; -} -.pinkbg:hover:after, -.pinkbg:focus:after, -.pinkbg:hover:before, -.pinkbg:focus:before { - background: #992B52!important; -} - -.turquoisebg { - background: #1ABC9C!important; -} -.turquoisebg:hover:after, -.turquoisebg:focus:after, -.turquoisebg:hover:before, -.turquoisebg:focus:before { - background: #0B4C3F!important; -} -.lightyellow { - background: #FFC973!important; -} -.lightyellow:hover:after, -.lightyellow:focus:after, -.lightyellow:hover:before, -.lightyellow:focus:before { - background: #F39500!important; -} -.lightgreen { - background: #B5F36D!important; -} -.lightgreen:hover:after, -.lightgreen:focus:after, -.lightgreen:hover:before, -.lightgreen:focus:before { - background: #77CF11!important; -} -.purpledarkbg { - background: #8E44AD!important; -} -.purpledarkbg:hover:after, -.purpledarkbg:focus:after, -.purpledarkbg:hover:before, -.purpledarkbg:focus:before { - background: #432051!important; -} diff --git a/portal/assets/themes/light/custom_portal.js b/portal/assets/themes/light/custom_portal.js deleted file mode 100644 index 7849e7c..0000000 --- a/portal/assets/themes/light/custom_portal.js +++ /dev/null @@ -1,40 +0,0 @@ -/* -=============================================================================== - This JS file may be used to customize the YunoHost user portal *and* also - will be loaded in all app pages if the app nginx's conf does include the - appropriate snippet. - - You can monkeypatch init_portal (loading of the user portal) and - init_portal_button_and_overlay (loading of the button and overlay...) to do - custom stuff -=============================================================================== -*/ - -var app_tile_colors = ['redbg','purpledarkbg','darkbluebg','orangebg','greenbg', 'yellowbg','lightpinkbg','pinkbg','turquoisebg','lightbluebg', 'bluebg']; - -function set_app_tile_style(el) -{ - // Select a color value from the App label - randomColorNumber = parseInt(el.textContent, 36) % app_tile_colors.length; - // Add color class. - el.classList.add(app_tile_colors[randomColorNumber]); -} - -// Monkeypatch init_portal to customize the app tile style -init_portal_original = init_portal; -init_portal = function() -{ - init_portal_original(); - Array.each(document.getElementsByClassName("app-tile"), set_app_tile_style); -} - -/* - * Monkey patching example to do custom stuff when loading inside an app - * -init_portal_button_and_overlay_original = init_portal_button_and_overlay; -init_portal_button_and_overlay = function() -{ - init_portal_button_and_overlay_original(); - // Custom stuff to do when loading inside an app -} -*/ diff --git a/portal/assets/themes/unsplash/cloud.png b/portal/assets/themes/unsplash/cloud.png deleted file mode 100644 index ad3ea55..0000000 Binary files a/portal/assets/themes/unsplash/cloud.png and /dev/null differ diff --git a/portal/assets/themes/unsplash/custom_overlay.css b/portal/assets/themes/unsplash/custom_overlay.css deleted file mode 100644 index 7f1a000..0000000 --- a/portal/assets/themes/unsplash/custom_overlay.css +++ /dev/null @@ -1,17 +0,0 @@ -/* -=============================================================================== - This file may contain extra CSS rules loaded on all apps page (*if* the app - nginx's conf does include the appropriate snippet) for the small YunoHost - button in bottom-right corner + portal overlay. - - The yunohost button corresponds to : #ynh-overlay-switch - The yunohost portal overlay / iframe corresponds to : #ynh-overlay - - BE CAREFUL that you should *not* add too-general rules that apply to - non-yunohost elements (for instance all 'a' or 'p' elements...) as it will - likely break app's rendering -=============================================================================== -*/ -#ynh-overlay-switch { - background-image: url("./cloud.png"); -} diff --git a/portal/assets/themes/unsplash/custom_portal.css b/portal/assets/themes/unsplash/custom_portal.css deleted file mode 100644 index 00d82a8..0000000 --- a/portal/assets/themes/unsplash/custom_portal.css +++ /dev/null @@ -1,78 +0,0 @@ -/* -=============================================================================== - This file contain extra CSS rules to customize the YunoHost user portal and - can be used to customize app tiles, buttons, etc... -=============================================================================== -*/ - -/* Make page texts white */ -.user-container h2, -.user-container small, -.user-container .user-mail, -.user-container .user-mail, -.content .footer a, -a.app-tile, -#ynh-logout { - color: white !important; -} - -body { - color: white !important; - text-shadow: 3px 4px 4px rgba(0,0,0,.4), -1px -1px 6px rgba(0,0,0,0.2); -} - -.ynh-user-portal { - background-image: url('https://source.unsplash.com/random/featured/?nature') !important; - background-repeat: no-repeat; - background-size: cover; - width: 100%; - height: 100%; -} - -/* Apps colors */ -.app-tile { - background-color: rgba(255, 255, 255, 0.5) !important; -} - -.app-tile:hover:after, -.app-tile:focus:after, -.app-tile:hover:before, -.app-tile:focus:before { - background: rgba(255, 255, 255, 0.5) !important; -} - -/* Use a custom logo image */ -#ynh-logo { - z-index: 10; - background-image: url("./cloud.png"); -} - -/* Round the form */ -.login-form label:before { - border-top-left-radius: 5em ; - border-bottom-left-radius: 5em ; -} - -.login-form * { - border-radius: 5em; -} - -/* Make form black */ - -.login-form label::before { - background: #000; - color: #FFF; -} - -.login-form .form-group * { - background: #000; - color: #FFF; -} - -.icon { - background: #000; -} - -.messages { - border-radius: .5em; -} diff --git a/portal/assets/themes/vapor/custom_overlay.css b/portal/assets/themes/vapor/custom_overlay.css deleted file mode 100644 index 0074f3e..0000000 --- a/portal/assets/themes/vapor/custom_overlay.css +++ /dev/null @@ -1,14 +0,0 @@ -/* -=============================================================================== - This file may contain extra CSS rules loaded on all apps page (*if* the app - nginx's conf does include the appropriate snippet) for the small YunoHost - button in bottom-right corner + portal overlay. - - The yunohost button corresponds to : #ynh-overlay-switch - The yunohost portal overlay / iframe corresponds to : #ynh-overlay - - BE CAREFUL that you should *not* add too-general rules that apply to - non-yunohost elements (for instance all 'a' or 'p' elements...) as it will - likely break app's rendering -=============================================================================== -*/ diff --git a/portal/assets/themes/vapor/custom_portal.css b/portal/assets/themes/vapor/custom_portal.css deleted file mode 100644 index ead4d5b..0000000 --- a/portal/assets/themes/vapor/custom_portal.css +++ /dev/null @@ -1,109 +0,0 @@ -/* -=============================================================================== - This file contain extra CSS rules to customize the YunoHost user portal and - can be used to customize app tiles, buttons, etc... -=============================================================================== -*/ - -/* ========================================================================== - Vaporwave theme - ========================================================================== */ -.ynh-user-portal { - min-height: 100vh; - background: rgb(205, 118, 255) !important; - background: -moz-linear-gradient(45deg, rgb(205, 118, 255) 0%, rgb(93, 150, 168) 100%) !important; - background: -webkit-gradient(linear, left bottom, right top, color-stop(0%, rgb(205, 118, 255)), color-stop(100%, rgb(93, 150, 168))) !important; - background: -webkit-linear-gradient(45deg, rgb(205, 118, 255) 0%, rgb(93, 150, 168) 100%) !important; - background: -o-linear-gradient(45deg, rgb(205, 118, 255) 0%, rgb(93, 150, 168) 100%) !important; - background: -ms-linear-gradient(45deg, rgb(205, 118, 255) 0%, rgb(93, 150, 168) 100%) !important; - background: linear-gradient(45deg, rgb(205, 118, 255) 0%, rgb(93, 150, 168) 100%) !important; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#C82BFF', endColorstr='#0C76A8', GradientType=1) !important; -} - -.messages.danger { background: #c0392b80; } -.messages.warning { background: #e67e2280; } -.messages.success { background: #27ae6080; } -.messages.info { background: #2980b980; } - -a, small, span, -.ynh-wrapper.footer a, -.user-menu a, -.user-container.user-container-info span, -input.btn.classic-btn.large-btn { - color: #e0e0e0 !important; -} - -.form-group input::placeholder, -.form-group input::-ms-input-placeholder, -.form-group input:-ms-input-placeholder { - color: #f4f4f4 !important; -} - -form.login-form input { - color: #222 !important; -} - -a:hover, -a:active, -a:focus, -.form-group input, -input.btn.classic-btn.large-btn:hover, -.ynh-wrapper.footer a:hover { - color: white !important; -} - -.ynh-wrapper.footer a:before { - color: #cc45ee !important; -} - -.ynh-wrapper.footer nav { - border-color: #cc45ee !important; -} - -.listing-apps li a span, -.listing-apps li a:hover span, -.listing-apps li a:active span, -.listing-apps li a:focus span { - color: white !important; -} - -.listing-apps li, -.listing-apps li a { - transition: all 0.3s ease-in-out, background 0ms; /* fix gray flicker on initial load */ - border: none transparent !important; - box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.1), - -2px -2px 3px 0 rgba(0, 0, 0, 0.1) inset; -} - -.listing-apps li:hover, -.listing-apps li a:hover { - box-shadow: 2px 2px 3px rgba(0, 0, 0, 0), - -2px -2px 3px 0 rgba(0, 0, 0, 0) inset; -} - -.btn.large-btn.classic-btn, -.btn.large-btn.validate-btn { - background: rgba(200, 200, 200, 0.4) !important; -} - -.btn.large-btn.classic-btn:hover, -.btn.large-btn.validate-btn:hover { - background: rgba(255, 255, 255, 0.4) !important; -} - -/* There are no colors, there is only vapor! */ -.app-tile, -.form-group input, -.form-group label, -a.btn:hover, -.btn.large-btn { - background: rgba(200, 200, 200, 0.2) !important; - border: none; -} - -.app-tile:hover:after, -.app-tile:focus:after, -.app-tile:hover:before, -.app-tile:focus:before { - background: rgba(200, 200, 200, 0.4) !important; -} diff --git a/portal/assets/themes/vapor/custom_portal.js b/portal/assets/themes/vapor/custom_portal.js deleted file mode 100644 index 80c27bd..0000000 --- a/portal/assets/themes/vapor/custom_portal.js +++ /dev/null @@ -1,33 +0,0 @@ -/* -=============================================================================== - This JS file may be used to customize the YunoHost user portal *and* also - will be loaded in all app pages if the app nginx's conf does include the - appropriate snippet. - - You can monkeypatch init_portal (loading of the user portal) and - init_portal_button_and_overlay (loading of the button and overlay...) to do - custom stuff -=============================================================================== -*/ - -/* - * Monkeypatch init_portal to customize the app tile style - * -init_portal_original = init_portal; -init_portal = function() -{ - init_portal_original(); - // Some stuff here -} -*/ - -/* - * Monkey patching example to do custom stuff when loading inside an app - * -init_portal_button_and_overlay_original = init_portal_button_and_overlay; -init_portal_button_and_overlay = function() -{ - init_portal_button_and_overlay_original(); - // Custom stuff to do when loading inside an app -} -*/ diff --git a/portal/edit.html b/portal/edit.html deleted file mode 100644 index d3f58c0..0000000 --- a/portal/edit.html +++ /dev/null @@ -1,59 +0,0 @@ - - -
-
- -
-
- - -
-
- - - -
-
- -
-
- - - {{#mailalias}} - - {{/mailalias}} - - {{t_add_mail}} -
- -
- - {{#maildrop}} - - {{/maildrop}} - - {{t_add_forward}} -
-
- -
- {{t_cancel}} - -
- - - -
-
diff --git a/portal/footer.ms b/portal/footer.ms deleted file mode 100644 index 992a577..0000000 --- a/portal/footer.ms +++ /dev/null @@ -1,18 +0,0 @@ - {{#connected}} - - {{/connected}} - - - - - - {{#theme}} - - {{/theme}} - - diff --git a/portal/header.ms b/portal/header.ms deleted file mode 100644 index 6bc00b9..0000000 --- a/portal/header.ms +++ /dev/null @@ -1,53 +0,0 @@ - - - - - {{t_portal}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {{#flash_win}} -
{{.}}
- {{/flash_win}} - - {{#flash_fail}} -
{{.}}
- {{/flash_fail}} - - {{#flash_info}} -
{{.}}
- {{/flash_info}} diff --git a/portal/index.html b/portal/index.html new file mode 100644 index 0000000..a759fdd --- /dev/null +++ b/portal/index.html @@ -0,0 +1,59 @@ + + +
+ +

+ + +

+ +

+ + +

+ + +
+ +
+
+ + + + + + diff --git a/portal/locales/ar.json b/portal/locales/ar.json deleted file mode 100644 index f94093f..0000000 --- a/portal/locales/ar.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "portal": "بوابة يونوهوست", - "information": "معلوماتك", - "username": "إسم المستخدم", - "password": "كلمة السر", - "fullname": "الإسم الكامل", - "mail_addresses": "عناوين البريد الإلكترونية", - "mail_forward": "عناوين توجيه البريد الإلكتروني", - "new_mail": "newmail@mydomain.org", - "new_forward": "newforward@myforeigndomain.org", - "add_mail": "إضافة عنوان بريد إلكتروني مستعار", - "add_forward": "إضافة عنوان آخر لتوجيه البريد", - "ok": "موافق", - "cancel": "إلغاء", - "change_password": "تعديل كلمة السر", - "edit": "تعديل", - "current_password": "كلمة السر الحالية", - "new_password": "كلمة السر الجديدة", - "confirm": "تأكيد", - "login": "لِج", - "logout": "الخروج", - "password_changed": "تم تغيير الكلمة السرية", - "password_changed_error": "لا يمكن تعديل الكلمة السرية", - "password_not_match": "كلمات السر غير متطابقة", - "wrong_current_password": "كلمة السر الحالية خاطئة", - "invalid_mail": "عنوان البريد الإلكتروني غير صالح", - "invalid_domain": "النطاق غير صالح في", - "invalid_mailforward": "عنوان بريد التحويل غير صالح", - "mail_already_used": "عنوان البريد الإلكتروني مُستعمل مِن قَبل", - "information_updated": "تم تحديث المعلومات", - "user_saving_fail": "لا يمكن حفظ معلومات المستخدم", - "missing_required_fields": "يُرجى ملئ الخانات المطلوبة", - "wrong_username_password": "إسم المستخدم أو كلمة السر خاطئة", - "logged_out": "تم تسجيل خروجك", - "please_login": "يرجى تسجيل الدخول قصد النفاذ إلى هذا المحتوى", - "please_login_from_portal": "يرجى تسجيل الدخول عبر البوابة", - "redirection_error_invalid_url": "خطأ في التحويل : عنوان الرابط غير صالح", - "redirection_error_unmanaged_domain": "خطأ في التحويل : لا يمكن إدارة النطاق", - "footerlink_edit": "تعديل ملفي الشخصي", - "footerlink_documentation": "الدليل", - "footerlink_support": "المساعدة", - "footerlink_administration": "الإدارة", - "password_too_simple_1": "يجب أن يكون طول الكلمة السرية على الأقل 8 حروف", - "good_practices_about_user_password": "اختر كلمة مرور مكونة مِن 8 أحرف على الأقل - مع العِلم أنّه مِن الممارسات الجيدة استخدام الأطول (أي عبارة مرور) و/أو إستخدام أنواع مختلفة من الأحرف (الحروف الكبيرة والصغيرة والأرقان والحروف الخاصة).", - "password_too_simple_4": "يجب أن يكون طول الكلمة السرية 12 حرفًا على الأقل وأن تحتوي على أرقام وحروف علوية ودنيا وحروف رمزية", - "password_too_simple_3": "يجب أن يكون طول كلمة المرور 8 حروف على الأقل وأن تحتوي على أرقام وحروف علوية ودنيا وحروف رمزية", - "password_too_simple_2": "يجب أن يكون طول كلمة المرور 8 حروف على الأقل وأن تحتوي على أرقام وحروف علوية ودنيا", - "password_listed": "إنّ الكلمة السرية هذه من بين أكثر الكلمات السرية إستخداما في العالم. الرجاء إختيار شيء فريد مِن نوعه." -} diff --git a/portal/locales/bn_BD.json b/portal/locales/bn_BD.json deleted file mode 100644 index d129702..0000000 --- a/portal/locales/bn_BD.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "footerlink_administration": "প্রশাসন", - "footerlink_support": "সমর্থন", - "footerlink_documentation": "নথিপত্র", - "footerlink_edit": "আমার প্রোফাইল সম্পাদনা করুন", - "redirection_error_unmanaged_domain": "পুনঃনির্দেশ ত্রুটি: নিয়ন্ত্রণহীন ডোমেন", - "redirection_error_invalid_url": "পুনঃনির্দেশ ত্রুটি: অবৈধ ইউআরএল", - "please_login_from_portal": "পোর্টাল থেকে লগ ইন করুন", - "please_login": "এই সামগ্রীতে অ্যাক্সেস করতে লগ ইন করুন", - "logged_out": "প্রস্থান", - "wrong_username_password": "ভুল ব্যবহারকারী নাম বা পাসওয়ার্ড", - "missing_required_fields": "প্রয়োজনীয় ক্ষেত্রগুলি পূরণ করুন", - "user_saving_fail": "নতুন ব্যবহারকারীর তথ্য সংরক্ষণ করা যায়নি", - "information_updated": "তথ্য আপডেট হয়েছে", - "mail_already_used": "ই-মেইল ঠিকানা ইতিমধ্যে ব্যবহৃত", - "invalid_mailforward": "অবৈধ ইমেল ফরোয়ার্ডিং ঠিকানা", - "invalid_domain": "এতে অবৈধ ডোমেন", - "invalid_mail": "অকার্যকর ইমেইল ঠিকানা", - "wrong_current_password": "বর্তমান পাসওয়ার্ডটি ভুল", - "good_practices_about_user_password": "কমপক্ষে 8 টি অক্ষরের ব্যবহারকারীর পাসওয়ার্ডটি চয়ন করুন - যদিও এটি দীর্ঘতর (যেমন একটি পাসফ্রেজ) এবং / অথবা বিভিন্ন ধরণের অক্ষর (বড় হাতের অক্ষর, ছোট হাতের অক্ষর এবং বিশেষ অক্ষর) ব্যবহার করা ভাল অনুশীলন।", - "password_too_simple_4": "পাসওয়ার্ডটিতে কমপক্ষে 12 টি অক্ষর দীর্ঘ হওয়া দরকার এবং এতে অঙ্ক, উপরের, নিম্ন এবং বিশেষ অক্ষরগুলি থাকে", - "password_too_simple_3": "পাসওয়ার্ডটিতে কমপক্ষে 8 টি অক্ষর দীর্ঘ হওয়া দরকার এবং এতে অঙ্ক, উপরের, নিম্ন এবং বিশেষ অক্ষরগুলি থাকে", - "password_too_simple_2": "পাসওয়ার্ডটিতে কমপক্ষে 8 টি অক্ষর দীর্ঘ হওয়া দরকার এবং এতে অঙ্ক, উপরের এবং নীচের অক্ষরগুলি থাকে", - "password_too_simple_1": "পাসওয়ার্ডটি কমপক্ষে 8 টি অক্ষরের দীর্ঘ হওয়া দরকার", - "password_listed": "এই পাসওয়ার্ডটি বিশ্বের সর্বাধিক ব্যবহৃত পাসওয়ার্ডগুলির মধ্যে রয়েছে। দয়া করে কিছুটা অনন্য কিছু চয়ন করুন।", - "password_not_match": "পাসওয়ার্ড মেলে না", - "password_changed_error": "পাসওয়ার্ড পরিবর্তন করা যায়নি", - "password_changed": "পাসওয়ার্ড পরিবর্তন", - "logout": "প্রস্থান", - "login": "প্রবেশ করুন", - "confirm": "নিশ্চিত করুন", - "new_password": "নতুন পাসওয়ার্ড", - "current_password": "বর্তমান পাসওয়ার্ড", - "edit": "সম্পাদন করা", - "change_password": "পাসওয়ার্ড পরিবর্তন করুন", - "cancel": "বাতিল", - "ok": "ঠিক আছে", - "add_forward": "একটি ইমেল ফরোয়ার্ডিং ঠিকানা যুক্ত করুন", - "add_mail": "একটি ইমেল ওরফে যুক্ত করুন", - "new_forward": "newforward@myforeigndomain.org", - "new_mail": "newmail@mydomain.org", - "mail_forward": "ই-মেইল ফরওয়ার্ডিং ঠিকানা", - "mail_addresses": "ইমেইল ঠিকানা", - "fullname": "পুরো নাম", - "password": "পাসওয়ার্ড", - "username": "ব্যবহারকারীর নাম", - "information": "আপনার তথ্য", - "portal": "ইউনোহোস্ট পোর্টাল" -} diff --git a/portal/locales/br.json b/portal/locales/br.json deleted file mode 100644 index 0967ef4..0000000 --- a/portal/locales/br.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/portal/locales/ca.json b/portal/locales/ca.json deleted file mode 100644 index cc06bc8..0000000 --- a/portal/locales/ca.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "portal": "Portal YunoHost", - "information": "La teva informació", - "username": "Nom d'usuari", - "password": "Contrasenya", - "fullname": "Nom complet", - "mail_addresses": "Adreces de correu electrònic", - "new_mail": "nou_correu@domini.org", - "add_mail": "Afegir un àlies de correu electrònic", - "ok": "OK", - "cancel": "Cancel·lar", - "change_password": "Canvia la contrasenya", - "edit": "Editar", - "current_password": "Contrasenya actual", - "new_password": "Nova contrasenya", - "confirm": "Confirmar", - "login": "Iniciar sessió", - "logout": "Tancar sessió", - "password_changed": "Contrasenya canviada", - "password_changed_error": "No s'ha pogut canviar la contrasenya", - "password_not_match": "Les contrasenyes no coincideixen", - "wrong_current_password": "La contrasenya actual és incorrecta", - "invalid_mail": "El correu electrònic no és vàlid", - "invalid_domain": "Domini invàlid a", - "mail_already_used": "El correu electrònic ja utilitzat", - "information_updated": "Informació actualitzada", - "user_saving_fail": "No s'han pogut enregistrar les noves dades de l'usuari", - "missing_required_fields": "Ompliu els camps obligatoris", - "wrong_username_password": "Contrasenya o nom d'usuari incorrectes", - "logged_out": "Sessió tancada", - "please_login": "Inicieu sessió per accedir a aquest contingut", - "please_login_from_portal": "Si us plau, inicieu sessió des del portal", - "redirection_error_invalid_url": "Error de redirecció: URL no vàlida", - "redirection_error_unmanaged_domain": "Error de redirecció: domini no gestionat", - "footerlink_edit": "Editar el meu perfil", - "footerlink_documentation": "Documentació", - "footerlink_support": "Ajuda", - "footerlink_administration": "Administració", - "mail_forward": "Correu electrònic de reenviament", - "new_forward": "noureenviament@dominiextern.org", - "add_forward": "Afegir un correu electrònic de reenviament", - "invalid_mailforward": "Correu electrònic de reenviament invàlid", - "password_listed": "Aquesta contrasenya és una de les més utilitzades en el món. Si us plau utilitzeu-ne una més única.", - "password_too_simple_1": "La contrasenya ha de tenir un mínim de 8 caràcters", - "password_too_simple_2": "La contrasenya ha de tenir un mínim de 8 caràcters i ha de contenir dígits, majúscules i minúscules", - "password_too_simple_3": "La contrasenya ha de tenir un mínim de 8 caràcters i tenir dígits, majúscules, minúscules i caràcters especials", - "password_too_simple_4": "La contrasenya ha de tenir un mínim de 12 caràcters i tenir dígits, majúscules, minúscules i caràcters especials", - "good_practices_about_user_password": "Trieu una contrasenya d'un mínim de 8 caràcters ; tot i que és de bona pràctica utilitzar una contrasenya més llarga (és a dir una frase de contrasenya) i/o utilitzar diferents tipus de caràcters (majúscules, minúscules, dígits i caràcters especials)." -} diff --git a/portal/locales/ckb.json b/portal/locales/ckb.json deleted file mode 100644 index 0967ef4..0000000 --- a/portal/locales/ckb.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/portal/locales/cs.json b/portal/locales/cs.json deleted file mode 100644 index e225087..0000000 --- a/portal/locales/cs.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "add_mail": "Přidat e-mail alias", - "new_forward": "newforward@myforeigndomain.org", - "new_mail": "newmail@mydomain.org", - "mail_forward": "E-mail pro přeposílání", - "mail_addresses": "E-mailová adresa", - "fullname": "Jméno a příjmení", - "password": "Heslo", - "username": "Uživatelské jméno", - "information": "Vaše údaje", - "portal": "YunoHost Portál", - "footerlink_administration": "Administrace", - "footerlink_support": "Podpora", - "footerlink_documentation": "Dokumentace", - "footerlink_edit": "Upravit svůj profil", - "redirection_error_unmanaged_domain": "Chyba přesměrování: Doména není spravována", - "redirection_error_invalid_url": "Chyba přesměrování: Neplatné URL", - "please_login_from_portal": "Prosím přihlašte se z portálu", - "please_login": "Pro přístup k obsahu se prosím přihlašte", - "logged_out": "Jste odhlášen/a", - "wrong_username_password": "Chybné uživatelské jméno nebo heslo", - "missing_required_fields": "Vyplňte povinné údaje", - "user_saving_fail": "Nelze uložit uživatelské údaje", - "information_updated": "Údaje upraveny", - "mail_already_used": "Tato e-mailová adresa se už používá", - "invalid_mailforward": "Neplatná e-mailová adresa pro přeposílání", - "invalid_domain": "Neplatná doména v", - "invalid_mail": "Neplatná e-mailová adresa", - "wrong_current_password": "Současné heslo je chybné", - "good_practices_about_user_password": "Vyberte si heslo aspoň 8 znaků dlouhé - dobrou praxí je ale používat delší frázi a používat různé druhy znaků (velká a malá písmena, číslice a speciální znaky).", - "password_too_simple_4": "Heslo musí být aspoň 12 znaků dlouhé a obsahovat čísla, velká a malá písmena a speciální znaky", - "password_too_simple_3": "Heslo musí být aspoň 8 znaků dlouhé a obsahovat čísla, velká a malá písmena a speciální znaky", - "password_too_simple_2": "Heslo musí být aspoň 8 znaků dlouhé a obsahovat číslici, velká a malá písmena", - "password_too_simple_1": "Heslo musí být aspoň 8 znaků dlouhé", - "password_listed": "Toto heslo je jedním z nejpoužívanějších na světě. Zvolte si prosím něco jediněčnějšího.", - "password_not_match": "Hesla se neshodují", - "password_changed_error": "Heslo nebylo změněno", - "password_changed": "Heslo změněno", - "logout": "Odhlásit se", - "login": "Přihlásit se", - "confirm": "Potvrdit", - "new_password": "Nové heslo", - "current_password": "Současné heslo", - "edit": "Upravit", - "change_password": "Změnit heslo", - "cancel": "Storno", - "ok": "OK", - "add_forward": "Přidat e-mailovou adresu pro přeposílání" -} diff --git a/portal/locales/da.json b/portal/locales/da.json deleted file mode 100644 index 0967ef4..0000000 --- a/portal/locales/da.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/portal/locales/de.json b/portal/locales/de.json deleted file mode 100644 index 224fd49..0000000 --- a/portal/locales/de.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "add_forward": "E-Mail-Weiterleitung hinzufügen", - "add_mail": "E-Mail-Alias hinzufügen", - "cancel": "Abbrechen", - "change_password": "Passwort ändern", - "confirm": "Bestätigen", - "current_password": "Aktuelles Passwort", - "edit": "Bearbeiten", - "footerlink_administration": "Verwaltung", - "footerlink_documentation": "Dokumentation", - "footerlink_edit": "Mein Profil bearbeiten", - "footerlink_support": "Support", - "fullname": "Vollständiger Name", - "information": "Ihre Informationen", - "information_updated": "Informationen aktualisiert", - "invalid_domain": "Ungültige Domäne angegeben", - "invalid_mail": "Ungültige E-Mail-Adresse", - "invalid_mailforward": "Ungültige E-Mail-Weiterleitung", - "logged_out": "Abgemeldet", - "login": "Anmelden", - "logout": "Abmelden", - "mail_addresses": "E-Mail-Adressen", - "mail_already_used": "Diese E-Mail-Adresse wird bereits verwendet", - "mail_forward": "E-Mail-Weiterleitung", - "missing_required_fields": "Die notwendigen Felder müssen ausgefüllt werden", - "new_forward": "neueweiterleitung@anderedomain.org", - "new_mail": "neueadresse@meinedomain.org", - "new_password": "Neues Passwort", - "ok": "OK", - "password": "Passwort", - "password_changed": "Passwort geändert", - "password_changed_error": "Passwort konnte nicht geändert werden", - "password_not_match": "Die Passwörter stimmen nicht überein", - "please_login": "Bitte melden Sie sich an, um auf diese Inhalte zuzugreifen", - "please_login_from_portal": "Bitte melden Sie sich über das Portal an", - "portal": "YunoHost-Portal", - "user_saving_fail": "Neue Kontoinformationen konnten nicht gespeichert werden", - "username": "Benutzername", - "wrong_current_password": "Aktuelles Passwort ist falsch", - "wrong_username_password": "Falscher Anmeldename oder Passwort", - "redirection_error_invalid_url": "Fehler bei Weiterleitung: Ungültige URL", - "redirection_error_unmanaged_domain": "Fehler bei Weiterleitung: Nicht-verwaltete Domain", - "good_practices_about_user_password": "Wählen Sie ein Benutzerpasswort mit mindestens 8 Zeichen - es ist jedoch empfehlenswert, ein längeres Passwort (z.B. eine Passphrase) und/oder verschiedene Arten von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", - "password_too_simple_3": "Das Passwort muss mindestens 8 Zeichen lang sein und Grossbuchstaben, Kleinbuchstaben, Zahlen und Sonderzeichen enthalten", - "password_too_simple_2": "Das Passwort muss mindestens 8 Zeichen lang sein und Gross- und Kleinbuchstaben sowie Zahlen enthalten", - "password_listed": "Dieses Passwort zählt zu den meistgenutzten Passwörtern der Welt. Bitte wähle ein anderes, einzigartigeres Passwort.", - "password_too_simple_4": "Das Passwort muss mindestens 12 Zeichen lang sein und Grossbuchstaben, Kleinbuchstaben, Zahlen und Sonderzeichen enthalten", - "password_too_simple_1": "Das Passwort muss mindestens 8 Zeichen lang sein" -} diff --git a/portal/locales/el.json b/portal/locales/el.json deleted file mode 100644 index 9054d13..0000000 --- a/portal/locales/el.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "footerlink_administration": "Διαχείριση", - "footerlink_support": "Υποστήριξη", - "footerlink_documentation": "Τεκμηρίωση", - "footerlink_edit": "Επεξεργασία του προφίλ μου", - "redirection_error_unmanaged_domain": "Σφάλμα ανακατεύθυνσης: Μη διαχειριζόμενος τομέας", - "redirection_error_invalid_url": "Σφάλμα ανακατεύθυνσης: Μη έγκυρο URL", - "please_login_from_portal": "Συνδεθείτε από την πύλη", - "please_login": "Συνδεθείτε για πρόσβαση σε αυτό το περιεχόμενο", - "logged_out": "Αποσυνδέθηκα", - "wrong_username_password": "Λάθος όνομα χρήστη ή κωδικός", - "missing_required_fields": "Συμπληρώστε τα απαιτούμενα πεδία", - "user_saving_fail": "Δεν ήταν δυνατή η αποθήκευση νέων πληροφοριών χρήστη", - "information_updated": "Οι πληροφορίες ενημερώθηκαν", - "mail_already_used": "Γίνεται ήδη χρήση της διεύθυνσης ηλεκτρονικού ταχυδρομείου", - "invalid_mailforward": "Μη έγκυρη διεύθυνση προώθησης e-mail", - "invalid_domain": "Μη έγκυρος τομέας στο", - "invalid_mail": "Μη έγκυρη διεύθυνση e-mail", - "wrong_current_password": "Ο τρέχων κωδικός πρόσβασης είναι λάθος", - "good_practices_about_user_password": "Διαλέξτε έναν κωδικό πρόσβασης χρήστη με τουλάχιστον 8 χαρακτήρες - αν και είναι καλή πρακτική να χρησιμοποιείτε μακρύτερους (δηλαδή μια φράση πρόσβασης) ή / και να χρησιμοποιείτε διάφορους τύπους χαρακτήρων (κεφαλαία, πεζά, ψηφία και ειδικούς χαρακτήρες).", - "password_too_simple_4": "Ο κωδικός πρόσβασης πρέπει να έχει μήκος τουλάχιστον 12 χαρακτήρων και περιέχει ψηφία, άνω, κάτω και ειδικούς χαρακτήρες", - "password_too_simple_3": "Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 8 χαρακτήρες και περιέχει ψηφία, άνω, κάτω και ειδικούς χαρακτήρες", - "password_too_simple_2": "Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 8 χαρακτήρες και περιέχει ψηφία, άνω και κάτω χαρακτήρες", - "password_too_simple_1": "Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 8 χαρακτήρες", - "password_listed": "Αυτός ο κωδικός πρόσβασης είναι από τους πιο χρησιμοποιούμενους κωδικούς πρόσβασης στον κόσμο. Επιλέξτε κάτι λίγο πιο μοναδικό.", - "password_not_match": "Οι κωδικοί πρόσβασης δεν ταιριάζουν", - "password_changed_error": "Δεν ήταν δυνατή η αλλαγή κωδικού πρόσβασης", - "password_changed": "Ο κωδικός άλλαξε", - "logout": "Αποσύνδεση", - "login": "Σύνδεση", - "confirm": "Επιβεβαιώνω", - "new_password": "Νέος Κωδικός", - "current_password": "Τρέχων κωδικός πρόσβασης", - "edit": "Επεξεργασία", - "change_password": "Αλλαξε κωδικό", - "cancel": "Ματαίωση", - "ok": "Εντάξει", - "add_forward": "Προσθέστε μια διεύθυνση προώθησης email", - "add_mail": "Προσθέστε ένα ψευδώνυμο email", - "new_forward": "νέοπροςταεμπρός@οξένοςτομέαςμου.org", - "new_mail": "νέοταχυδρομείο@οτομέαςμου.org", - "mail_forward": "Διεύθυνση προώθησης ηλεκτρονικού ταχυδρομείου", - "mail_addresses": "Διευθύνσεις ηλεκτρονικού ταχυδρομείου", - "fullname": "Πλήρες όνομα", - "password": "Κωδικός πρόσβασης", - "username": "Όνομα χρήστη", - "information": "Τα στοιχεία σας", - "portal": "Πύλη YunoHost" -} diff --git a/portal/locales/en.json b/portal/locales/en.json deleted file mode 100644 index 2d07f11..0000000 --- a/portal/locales/en.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "portal": "YunoHost Portal", - "information": "Your info", - "username": "Username", - "password": "Password", - "fullname": "Full name", - "mail_addresses": "E-mail addresses", - "mail_forward": "E-mail forwarding address", - "new_mail": "newmail@mydomain.org", - "new_forward": "newforward@myforeigndomain.org", - "add_mail": "Add an e-mail alias", - "add_forward": "Add an e-mail forwarding address", - "ok": "OK", - "cancel": "Cancel", - "change_password": "Change password", - "edit": "Edit", - "current_password": "Current password", - "new_password": "New password", - "confirm": "Confirm", - "login": "Log in", - "logout": "Log out", - "password_changed": "Password changed", - "password_changed_error": "Could not change password", - "password_not_match": "The passwords don't match", - "password_listed": "This password is among the most used passwords in the world. Please choose something a bit more unique.", - "password_too_simple_1": "The password needs to be at least 8 characters long", - "password_too_simple_2": "The password needs to be at least 8 characters long and contains digit, upper and lower characters", - "password_too_simple_3": "The password needs to be at least 8 characters long and contains digit, upper, lower and special characters", - "password_too_simple_4": "The password needs to be at least 12 characters long and contains digit, upper, lower and special characters", - "good_practices_about_user_password": "Pick a user password of at least 8 characters - though it is good practice to use longer ones (i.e. a passphrase) and/or use various kind of characters (uppercase, lowercase, digits and special characters).", - "wrong_current_password": "The current password is wrong", - "invalid_mail": "Invalid e-mail address", - "invalid_domain": "Invalid domain in", - "invalid_mailforward": "Invalid e-mail forwarding address", - "mail_already_used": "E-mail address already in use", - "information_updated": "Info updated", - "user_saving_fail": "Could not save new user info", - "missing_required_fields": "Fill in the required fields", - "wrong_username_password": "Wrong username or password", - "logged_out": "Logged out", - "please_login": "Please log in to access to this content", - "please_login_from_portal": "Please log in from the portal", - "redirection_error_invalid_url": "Redirection error: Invalid URL", - "redirection_error_unmanaged_domain": "Redirection error: Unmanaged domain", - "footerlink_edit": "Edit my profile", - "footerlink_documentation": "Documentation", - "footerlink_support": "Support", - "footerlink_administration": "Administration" -} diff --git a/portal/locales/eo.json b/portal/locales/eo.json deleted file mode 100644 index 03b53f3..0000000 --- a/portal/locales/eo.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "password": "Pasvorto", - "username": "Uzantnomo", - "mail_addresses": "Retpoŝtadresoj", - "information": "Via informoj", - "new_password": "Nova pasvorto", - "current_password": "Nuna pasvorto", - "login": "Ensaluti", - "logout": "Elsaluti", - "change_password": "Ŝanĝi pasvorton", - "edit": "Redakti", - "cancel": "Nuligi", - "portal": "Yunohost portalo", - "fullname": "Plena nomo", - "new_mail": "nova-adreso@mia-domajno.org", - "confirm": "Konfirmu", - "password_changed": "Pasvorto ŝanĝita", - "password_changed_error": "Ne povis ŝanĝi pasvorton", - "password_not_match": "La pasvortoj ne kongruas", - "footerlink_administration": "Administrado", - "footerlink_support": "Subteno", - "footerlink_documentation": "Dokumentado", - "footerlink_edit": "Redakti mian profilon", - "redirection_error_unmanaged_domain": "Redirekta eraro: Ne administrita domajno", - "redirection_error_invalid_url": "Redirekta eraro: Nevalida URL", - "please_login_from_portal": "Bonvolu ensaluti de la portalo", - "please_login": "Bonvolu ensaluti por aliri ĉi tiun enhavon", - "logged_out": "Ensalutinta", - "wrong_username_password": "Malĝusta uzantnomo aŭ pasvorto", - "missing_required_fields": "Plenigu la postulatajn kampojn", - "user_saving_fail": "Ne povis konservi novajn uzantinformojn", - "information_updated": "Informoj ĝisdatigitaj", - "mail_already_used": "Retpoŝtadreso jam en uzo", - "invalid_mailforward": "Nevalida retpoŝtadreso", - "invalid_domain": "Nevalida domajno en", - "invalid_mail": "Nevalida retpoŝta adreso", - "wrong_current_password": "Aktuala pasvorto estas malĝusta", - "good_practices_about_user_password": "Elektu uzantan pasvorton de almenaŭ 8 signoj - kvankam ĝi estas bona praktiko uzi pli longajn (I.E. Pasfraso) kaj / aŭ uzas diversajn specojn de karakteroj (majusklaj, minusklaj, ciferoj kaj specialaj signoj).", - "password_too_simple_4": "La pasvorto devas havi almenaŭ 12 signojn kaj enhavas ciferojn, suprajn, pli malaltajn kaj specialajn signojn", - "password_too_simple_3": "La pasvorto devas havi almenaŭ 8 signojn kaj enhavas ciferojn, suprajn, pli malaltajn kaj specialajn signojn", - "password_too_simple_2": "La pasvorto devas havi almenaŭ 8 signojn kaj enhavas ciferojn, suprajn kaj pli malaltajn signojn", - "password_too_simple_1": "Pasvorto devas esti almenaŭ 8 signojn longa", - "password_listed": "Ĉi tiu pasvorto estas inter la plej uzataj pasvortoj en la mondo. Bonvolu elekti ion pli unikan.", - "ok": "bone", - "add_forward": "Aldonu poŝton antaŭen", - "add_mail": "Aldonu poŝton alias", - "new_forward": "newforward@myforeigndomain.org", - "mail_forward": "Poŝti antaŭen" -} diff --git a/portal/locales/es.json b/portal/locales/es.json deleted file mode 100644 index 6e7328a..0000000 --- a/portal/locales/es.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "add_forward": "Añadir una dirección de reenvío de correo electrónico", - "add_mail": "Añadir un alias de correo electrónico", - "cancel": "Cancelar", - "change_password": "Cambiar contraseña", - "confirm": "Confirmar", - "current_password": "Contraseña actual", - "edit": "Editar", - "footerlink_administration": "Administración", - "footerlink_documentation": "Documentación", - "footerlink_edit": "Editar mi perfil", - "footerlink_support": "Ayuda", - "fullname": "Nombre completo", - "information": "Su información", - "information_updated": "Información actualizada", - "invalid_domain": "Dominio no válido en", - "invalid_mail": "La dirección de correo electrónico no es válida", - "invalid_mailforward": "La dirección de reenvío de correo electrónico no es válida", - "logged_out": "Sesión cerrada", - "login": "Iniciar sesión", - "logout": "Cerrar sesión", - "mail_addresses": "Direcciones de correo electrónico", - "mail_already_used": "Dirección de correo electrónico ya está en uso", - "mail_forward": "Direcciones de reenvío de correo electrónico", - "missing_required_fields": "Faltan campos obligatorios", - "new_forward": "nuevoreenvio@midominioexterior.org", - "new_mail": "nuevomail@midominio.org", - "new_password": "Nueva contraseña", - "ok": "OK", - "password": "Contraseña", - "password_changed": "Contraseña cambiada correctamente", - "password_changed_error": "Se produjo un error cambiando la contraseña", - "password_not_match": "Las nuevas contraseñas no coinciden", - "please_login": "Inicie sesión para acceder a este contenido", - "please_login_from_portal": "Por favor, inicie sesión desde el portal", - "portal": "Portal YunoHost", - "user_saving_fail": "Se produjo un error al guardar los cambios del usuario", - "username": "Nombre de usuario", - "wrong_current_password": "La contraseña actual es incorrecta", - "wrong_username_password": "Nombre de usuario o contraseña incorrectos", - "redirection_error_invalid_url": "Error de redirección: url inválido", - "redirection_error_unmanaged_domain": "Error de redirección: Dominio no gestionado", - "password_listed": "Esta contraseña se encuentra entre las contraseñas más utilizadas en el mundo. Por favor, elija algo un poco más único.", - "password_too_simple_1": "La contraseña debe tener al menos 8 caracteres de longitud", - "password_too_simple_2": "La contraseña debe tener al menos 8 caracteres de longitud y contiene dígitos, mayúsculas y minúsculas", - "password_too_simple_3": "La contraseña debe ser de al menos 8 caracteres de longitud e incluir un número y caracteres en mayúsculas, minúsculas y caracteres especiales", - "password_too_simple_4": "La contraseña debe ser de al menos 12 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales", - "good_practices_about_user_password": "Está a punto de establecer una nueva contraseña de usuario. La contraseña debería de ser de al menos 8 caracteres, aunque es una buena práctica usar una contraseña más larga (es decir, una frase de paso) y/o usar varias clases de caracteres (mayúsculas, minúsculas, dígitos y caracteres especiales)." -} diff --git a/portal/locales/eu.json b/portal/locales/eu.json deleted file mode 100644 index f1dd576..0000000 --- a/portal/locales/eu.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "footerlink_administration": "Administrazioa", - "footerlink_support": "Laguntza", - "footerlink_documentation": "Dokumentazioa", - "footerlink_edit": "Editatu profila", - "redirection_error_unmanaged_domain": "Birzuzenketa errorea: kudeatu gabeko domeinua", - "redirection_error_invalid_url": "Birbideraketa errorea: URL okerra", - "please_login_from_portal": "Hasi saioa atarian", - "please_login": "Hasi saioa edukira sartzeko", - "logged_out": "Saioa amaituta", - "wrong_username_password": "Erabiltzaile-izen edo pasahitz okerra", - "missing_required_fields": "Bete beharreko eremuak", - "user_saving_fail": "Ezinezkoa izan da erabiltzailearen informazio berria gordetzea", - "information_updated": "Informazioa eguneratu da", - "mail_already_used": "Helbide elektroniko hori erabiltzen ari zara dagoeneko", - "invalid_mailforward": "Birbidalketarako helbide okerra", - "invalid_domain": "Domeinu okerra", - "invalid_mail": "Helbide elektronikoa ez da zuzena", - "wrong_current_password": "Oraingo pasahitza okerra da", - "good_practices_about_user_password": "Aukeratu gutxienez 8 karaktere dituen erabiltzaile-pasahitz bat — baina gomendioa pasahitz luzeagoak erabiltzea da (adibidez, esaldi bat) edota karaktere desberdinak erabiltzea (larriak, txikiak, zenbakiak eta karaktere bereziak).", - "password_too_simple_4": "Pasahitzak 12 karaktere izan behar ditu gutxienez eta zenbakiren bat, hizki larriren bat, txikiren bat eta karaktere bereziren bat izan behar ditu", - "password_too_simple_3": "Pasahitzak 8 karaktere izan behar ditu gutxienez eta zenbakiak, hizki larriak, hizki txikiak eta karaktere bereziak izan behar ditu", - "password_too_simple_2": "Pasahitzak 8 karaktere izan behar ditu gutxienez eta zenbakiak, hizki larriak eta hizki txikiak izan behar ditu", - "password_too_simple_1": "Pasahitzak 8 karaktere izan behar ditu gutxienez", - "password_listed": "Pasahitz hau munduko pasahitz erabilienen artean dago. Aukeratu bereziagoa den zerbait.", - "password_not_match": "Pasahitzak ez datoz bat", - "password_changed_error": "Ezin izan da pasahitza aldatu", - "password_changed": "Pasahitza aldatu da", - "logout": "Amaitu saioa", - "login": "Hasi saioa", - "confirm": "Berretsi", - "new_password": "Pasahitz berria", - "current_password": "Oraingo pasahitza", - "edit": "Editatu", - "change_password": "Aldatu pasahitza", - "cancel": "Utzi", - "ok": "Ados", - "add_forward": "Gehitu helbide elektronikoa birbidaltzeko e-maila", - "add_mail": "Gehitu e-mail ezizen bat", - "new_forward": "birbidalketaberria@nirekanpokodomeinua.eus", - "new_mail": "postaberria@niredomeinua.eus", - "mail_forward": "Birbidalketarako posta elektronikoa", - "mail_addresses": "Helbide elektronikoak", - "fullname": "Izen osoa", - "password": "Pasahitza", - "username": "Erabiltzaile-izena", - "information": "Zure informazioa", - "portal": "YunoHost ataria" -} diff --git a/portal/locales/fa.json b/portal/locales/fa.json deleted file mode 100644 index 40942a2..0000000 --- a/portal/locales/fa.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "cancel": "لغو", - "logged_out": "خارج شده", - "password": "کلمه عبور", - "ok": "خوب", - "footerlink_administration": "مدیریت", - "footerlink_support": "پشتیبانی", - "footerlink_documentation": "مستندات", - "footerlink_edit": "ویرایش پروفایل من", - "redirection_error_unmanaged_domain": "خطای تغییر مسیر: دامنه مدیریت نشده", - "redirection_error_invalid_url": "خطای تغییر مسیر: نشانی اینترنتی نامعتبر است", - "please_login_from_portal": "لطفاً از درگاه پورتال وارد شوید", - "please_login": "لطفاً برای دسترسی به این محتوا وارد شوید", - "wrong_username_password": "نام کاربری یا رمز عبور اشتباه است", - "missing_required_fields": "فیلدهای مورد نیاز را پر کنید", - "user_saving_fail": "اطلاعات کاربر جدید ذخیره نشد", - "information_updated": "اطلاعات به روز شد", - "mail_already_used": "آدرس پست الکترونیکی قبلاً استفاده می شود", - "invalid_mailforward": "آدرس ارسال ایمیل نامعتبر است", - "invalid_domain": "دامنه نامعتبر در", - "invalid_mail": "آدرس ایمیل نامعتبر است", - "wrong_current_password": "رمز فعلی اشتباه است", - "good_practices_about_user_password": "گذرواژه کاربر متشکل ازانواع مختلف کاراکترها (بزرگ ، کوچک ، رقم و کاراکتر های خاص)را حداقل با 8 کاراکتر انتخاب کنید - هرچند استفاده از کلمات طولانی تر تمرین خوبی است (مانند عبارت عبور).", - "password_too_simple_4": "رمز عبور باید شامل اعداد ، حروف کوچک و بزرگ و کاراکترهای خاص باشد، و حداقل 12 کاراکتر طول داشته باشد", - "password_too_simple_3": "رمز عبور باید شامل اعداد ، حروف کوچک و بزرگ و کاراکترهای خاص باشد، و حداقل 8 کاراکتر طول داشته باشد", - "password_too_simple_2": "رمز عبور باید شامل اعداد و حروف کوچک و بزرگ، و حداقل 8 کاراکتر طول داشته باشد", - "password_too_simple_1": "رمز عبور باید حداقل 8 کاراکتر باشد", - "password_listed": "لطفاً گذرواژه کمی منحصر به فردتری انتخاب کنید. این رمز عبور جزو پر استفاده ترین رمزهای عبور جهان بشمار میرود.", - "password_not_match": "گذرواژه ها مطابقت ندارند", - "password_changed_error": "رمز عبور تغییر نکرد", - "password_changed": "رمز عبور تغییر کرد", - "logout": "خروج", - "login": "ورود به سیستم", - "confirm": "تائید کردن", - "new_password": "رمز عبور جدید", - "current_password": "رمز عبور فعلی", - "edit": "ویرایش", - "change_password": "تغییر رمز عبور", - "add_forward": "آدرس هدایت ایمیل را اضافه کنید", - "add_mail": "یک نام مستعار ایمیل اضافه کنید", - "new_forward": "newforward@myforeigndomain.org", - "new_mail": "newmail@mydomain.org", - "mail_forward": "آدرس ارسال به جلو ایمیل", - "mail_addresses": "آدرس ایمیل", - "fullname": "نام و نام خانوادگی", - "username": "نام کاربری", - "information": "اطلاعات شما", - "portal": "پورتال YunoHost" -} diff --git a/portal/locales/fi.json b/portal/locales/fi.json deleted file mode 100644 index b952cee..0000000 --- a/portal/locales/fi.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "cancel": "Peruuta", - "portal": "YunoHost-portaali", - "password": "Salasana", - "ok": "OK", - "information": "Sinun tiedot", - "username": "Käyttäjänimi", - "fullname": "Koko nimi", - "mail_addresses": "Sähköpostiosoitteet", - "mail_forward": "Sähköpostin välitysosoite", - "new_mail": "uusiosoite@minundomain.fi", - "new_forward": "uusivälitys@minunulkopuolinendomain.fi", - "add_mail": "Lisää sähköposti-alias", - "add_forward": "Lisää sähköpostin välitysosoite", - "change_password": "Vaihda salasana", - "edit": "Muokkaa", - "current_password": "Nykyinen salasana", - "new_password": "Uusi salasana", - "confirm": "Vahvista", - "login": "Kirjaudu sisään", - "logout": "Kirjaudu ulos", - "password_changed": "Salasana vaihdettu", - "password_changed_error": "Salasanaa ei voitu vaihtaa", - "password_not_match": "Salasanat eivät täsmänneet", - "password_listed": "Tämä salasana on yksi maailman käytetyimmistä salasanoista. Valitse jotain hieman ainutlaatuisempaa.", - "password_too_simple_1": "Salasanan pitää olla ainakin 8 merkin pituinen", - "password_too_simple_2": "Salasanan on oltava vähintään 8 merkkiä pitkä ja sen on sisällettävä numeroita, isoja ja pieniä merkkejä", - "wrong_current_password": "Nykyinen salasana on väärin", - "invalid_mail": "Virheellinen sähköpostiosoite", - "invalid_domain": "Virheellinen domain", - "invalid_mailforward": "Virheellinen välityssähköpostiosoite", - "mail_already_used": "Sähköpostiosoite on jo käytössä", - "information_updated": "Tiedot päivitetty", - "user_saving_fail": "Uuden käyttäjän tietoja ei voitu tallentaa", - "missing_required_fields": "Täytä pakolliset kentät", - "wrong_username_password": "Väärä käyttäjänimi tai salasana", - "logged_out": "Kirjauduttu ulos", - "please_login": "Kirjaudu sisään päästäksesi käsiksi tähän sisältöön", - "please_login_from_portal": "Kirjaudu sisään portaalista", - "redirection_error_invalid_url": "Uudelleenohjausvirhe: Virheellinen URL-osoite", - "redirection_error_unmanaged_domain": "Uudelleenohjausvirhe: Hallitsematon domain", - "footerlink_edit": "Muokkaa profiiliani", - "footerlink_documentation": "Dokumentaatio", - "footerlink_support": "Tuki", - "footerlink_administration": "Ylläpito", - "password_too_simple_3": "Salasanan on oltava vähintään 8 merkkiä pitkä ja sen on sisällettävä numeroita, isoja ja pieniä merkkejä", - "password_too_simple_4": "Salasanan on oltava vähintään 12 merkkiä pitkä ja sen on sisällettävä numeroita, isoja ja pieniä merkkejä", - "good_practices_about_user_password": "Valitse vähintään kahdeksan merkkiä pitkä salasana - on kuitenkin hyvä käyttää pidempiä salasanoja (esim. salasanalause) ja/tai erilaisia merkkejä (isoja ja pieniä kirjaimia, numeroita ja erikoismerkkejä)." -} diff --git a/portal/locales/fr.json b/portal/locales/fr.json deleted file mode 100644 index 5841db2..0000000 --- a/portal/locales/fr.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "add_forward": "Ajouter une adresse de transfert", - "add_mail": "Ajouter un alias de courriel", - "cancel": "Annuler", - "change_password": "Changer de mot de passe", - "confirm": "Confirmation", - "current_password": "Mot de passe actuel", - "edit": "Éditer", - "footerlink_administration": "Administration", - "footerlink_documentation": "Documentation", - "footerlink_edit": "Éditer mon profil", - "footerlink_support": "Support", - "fullname": "Nom complet", - "information": "Vos infos", - "information_updated": "Info mises à jour", - "invalid_domain": "Nom de domaine invalide dans", - "invalid_mail": "Adresse de courriel invalide", - "invalid_mailforward": "Adresse courriel de transfert invalide", - "logged_out": "Déconnecté", - "login": "Connexion", - "logout": "Déconnexion", - "mail_addresses": "Adresses de courriel", - "mail_already_used": "Adresse de courriel déjà utilisée", - "mail_forward": "Adresses de transfert", - "missing_required_fields": "Remplir les champs obligatoires", - "new_forward": "nouveau_transfert@domainedistant.org", - "new_mail": "nouvelle_adresse@domaine.org", - "new_password": "Nouveau mot de passe", - "ok": "OK", - "password": "Mot de passe", - "password_changed": "Mot de passe modifié", - "password_changed_error": "Impossible de changer le mot de passe", - "password_not_match": "Les mots de passe ne correspondent pas", - "please_login": "Veuillez vous identifier pour accéder à cette page", - "please_login_from_portal": "Veuillez vous identifier depuis le portail", - "portal": "Portail YunoHost", - "user_saving_fail": "Impossible d'enregistrer les nouvelles informations utilisateur", - "username": "Nom d’utilisateur", - "wrong_current_password": "Le mot de passe actuel est incorrect", - "wrong_username_password": "Nom d’utilisateur ou mot de passe incorrect", - "redirection_error_invalid_url": "Erreur de redirection : URL invalide", - "redirection_error_unmanaged_domain": "Erreur de redirection : domaine non géré", - "password_listed": "Ce mot de passe est l'un des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose d'un peu plus singulier.", - "password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères", - "password_too_simple_2": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules et des minuscules", - "password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", - "password_too_simple_4": "Le mot de passe doit comporter au moins 12 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux", - "good_practices_about_user_password": "Choisissez un mot de passe utilisateur d’au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou une combinaison de caractères (majuscules, minuscules, chiffres et caractères spéciaux)." -} diff --git a/portal/locales/gl.json b/portal/locales/gl.json deleted file mode 100644 index 495bff6..0000000 --- a/portal/locales/gl.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "footerlink_administration": "Administración", - "footerlink_support": "Axuda", - "footerlink_documentation": "Documentación", - "footerlink_edit": "Editar o meu perfil", - "redirection_error_unmanaged_domain": "Erro na redirección: Dominio non xestionado", - "redirection_error_invalid_url": "Erro na redirección: URL non válido", - "please_login_from_portal": "Conéctate desde o portal", - "please_login": "Conéctate para acceder a este contido", - "logged_out": "Sesión pechada", - "wrong_username_password": "Credenciais incorrectas", - "missing_required_fields": "Completa os campos requeridos", - "user_saving_fail": "Non se gardou a info da nova usuaria", - "information_updated": "Info actualizada", - "mail_already_used": "Xa está en uso o enderezo de email", - "invalid_mailforward": "Enderezo de reenvío de email non válido", - "invalid_domain": "Dominio non válido", - "invalid_mail": "Enderezo de email non válido", - "wrong_current_password": "O contrasinal actual é incorrecto", - "good_practices_about_user_password": "Elixe un contrasinal con 8 caracteres como mínimo - é recomendable que sexa longo (ex. unha frase) e utilizar varios tipos de caracteres (maiúsculas, minúsculas, díxitos e caracteres especiais).", - "password_too_simple_4": "O contrasinal debe ter 12 caracteres como mínimo e ter díxitos, maiúsculas e minúsculas e caracteres especiais", - "password_too_simple_3": "O contrasinal debe ter 8 caracteres como mínimo e ter díxitos, maiúsculas e minúsculas e caracteres especiais", - "password_too_simple_2": "O contrasinal debe ter 8 caracteres como mínimo e ter díxitos e caracteres en maiúsculas e minúsculas", - "password_too_simple_1": "O contrasinal ten que ter 8 caracteres como mínimo", - "password_listed": "Este contrasinal é un dos máis utilizados no mundo. Mellor elixe un que sexa máis orixinal.", - "password_not_match": "Os contrasinais non concordan", - "password_changed_error": "Non se cambiou o contrasinal", - "password_changed": "Contrasinal cambiado", - "logout": "Pechar sesión", - "login": "Acceder", - "confirm": "Confirmar", - "new_password": "Novo contrasinal", - "current_password": "Contrasinal actual", - "edit": "Editar", - "change_password": "Cambiar contrasinal", - "cancel": "Cancelar", - "ok": "Ok", - "add_forward": "Engadir un enderezo de reenvío de email", - "add_mail": "Engadir un alias de email", - "new_forward": "novoreenvio@omeudominioexterno.org", - "new_mail": "novomail@omeudominio.org", - "mail_forward": "Enderezo de reenvío de email", - "mail_addresses": "Enderezos de email", - "fullname": "Nome completo", - "password": "Contrasinal", - "username": "Identificador", - "information": "A túa info", - "portal": "Portal YunoHost" -} diff --git a/portal/locales/he.json b/portal/locales/he.json deleted file mode 100644 index 0967ef4..0000000 --- a/portal/locales/he.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/portal/locales/hi.json b/portal/locales/hi.json deleted file mode 100644 index b39e785..0000000 --- a/portal/locales/hi.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "logged_out": "लॉग आउट", - "password": "पासवर्ड", - "footerlink_administration": "प्रशासन", - "footerlink_support": "समर्थन", - "footerlink_documentation": "प्रलेखन", - "footerlink_edit": "मेरे प्रोफ़ाइल संपादित करे", - "redirection_error_unmanaged_domain": "पुनर्निर्देशन त्रुटि: अप्रबंधित डोमेन", - "redirection_error_invalid_url": "पुनर्निर्देशन त्रुटि: अमान्य URL", - "please_login_from_portal": "कृपया पोर्टल से लॉग इन करें", - "please_login": "कृपया इस सामग्री तक पहुंचने के लिए लॉग इन करें", - "wrong_username_password": "उपयोगकर्ता का गलत नाम और पासवर्ड", - "missing_required_fields": "आवश्यक फ़ील्ड भरें", - "user_saving_fail": "नई उपयोगकर्ता जानकारी को सहेज नहीं सका", - "information_updated": "जानकारी अपडेट की गई", - "mail_already_used": "यह ईमेल अड्रेस पहले से ही उपयोग में है", - "invalid_mailforward": "अमान्य ई-मेल अग्रेषण पता", - "invalid_domain": "में अमान्य डोमेन", - "invalid_mail": "अमान्य ईमेल पता", - "wrong_current_password": "वर्तमान पासवर्ड गलत है", - "good_practices_about_user_password": "कम से कम 8 वर्णों का एक उपयोगकर्ता पासवर्ड चुनें - हालाँकि यह लंबे लोगों (यानी एक पासफ़्रेज़) और / या विभिन्न प्रकार के वर्ण (अपरकेस, लोअरकेस, अंक और विशेष वर्ण) का उपयोग करने के लिए अच्छा अभ्यास है।", - "password_too_simple_4": "पासवर्ड को कम से कम 12 वर्णों का होना चाहिए और इसमें अंक, ऊपरी, निचले और विशेष वर्ण शामिल होने चाहिए", - "password_too_simple_3": "पासवर्ड को कम से कम 8 वर्ण लंबा होना चाहिए और इसमें अंक, ऊपरी, निचले और विशेष वर्ण शामिल हैं", - "password_too_simple_2": "पासवर्ड को कम से कम 8 वर्ण लंबा होना चाहिए और इसमें अंक, ऊपरी और निचले वर्ण शामिल हैं", - "password_too_simple_1": "पासवर्ड को कम से कम 8 वर्ण लंबा होना चाहिए", - "password_listed": "यह पासवर्ड दुनिया में सबसे ज्यादा इस्तेमाल किए जाने वाले पासवर्ड में से है। कृपया कुछ और अनोखा चुनें।", - "password_not_match": "पासवर्ड मेल नहीं खाते", - "password_changed_error": "पासवर्ड नहीं बदल सका", - "password_changed": "पासवर्ड बदला गया", - "logout": "लोग आउट", - "login": "लॉग इन करें", - "confirm": "की पुष्टि करें", - "new_password": "नया पासवर्ड", - "current_password": "वर्तमान पासवर्ड", - "edit": "संपादित करें", - "change_password": "पासवर्ड बदलें", - "cancel": "रद्द करना", - "ok": "ठीक है", - "add_forward": "एक ई-मेल अग्रेषण पता जोड़ें", - "add_mail": "एक ईमेल उपनाम जोड़ें", - "new_forward": "newforward@myforeigndomain.org", - "new_mail": "newmail@mydomain.org", - "mail_forward": "ई-मेल अग्रेषण पता", - "mail_addresses": "ईमेल पता", - "fullname": "पूरा नाम", - "username": "उपयोगकर्ता नाम", - "information": "आपकी जानकारी", - "portal": "यूनोहास्ट पोर्टल" -} diff --git a/portal/locales/hu.json b/portal/locales/hu.json deleted file mode 100644 index 47fc7aa..0000000 --- a/portal/locales/hu.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "footerlink_administration": "Adminisztráció", - "footerlink_support": "Támogatás", - "footerlink_documentation": "Dokumentáció", - "footerlink_edit": "Profilom szerkesztése", - "redirection_error_unmanaged_domain": "Átirányítási hiba: Nem kezelt domain", - "redirection_error_invalid_url": "Átirányítási hiba: érvénytelen URL", - "please_login_from_portal": "Kérjük, jelentkezzen be a portálról", - "please_login": "Kérjük, jelentkezzen be, hogy hozzáférjen ehhez a tartalomhoz", - "logged_out": "Kilépett", - "wrong_username_password": "Rossz felhasználónév vagy jelszó", - "missing_required_fields": "Töltse ki a kötelező mezőket", - "user_saving_fail": "Nem sikerült menteni az új felhasználói információkat", - "information_updated": "Az információ frissítve", - "mail_already_used": "Az e-mail cím már használatban van", - "invalid_mailforward": "Érvénytelen e-mail továbbító cím", - "invalid_domain": "Érvénytelen domain itt", - "invalid_mail": "Érvénytelen e-mail cím", - "wrong_current_password": "A jelenlegi jelszó helytelen", - "good_practices_about_user_password": "Válasszon legalább 8 karakterből álló felhasználói jelszót - jó gyakorlat azonban hosszabb jelszó használata (azaz egy jelmondat) és/vagy különféle karakterek (nagybetűk, kisbetűk, számjegyek és speciális karakterek) használata.", - "password_too_simple_4": "A jelszónak legalább 12 karakter hosszúnak kell lennie, és tartalmaznia kell számjegy, felső, alsó és speciális karaktereket", - "password_too_simple_3": "A jelszónak legalább 8 karakter hosszúnak kell lennie, és tartalmaznia kell számjegy, felső, alsó és speciális karaktereket", - "password_too_simple_2": "A jelszónak legalább 8 karakter hosszúnak kell lennie, és számjegyű, felső és alsó karaktereket kell tartalmaznia", - "password_too_simple_1": "A jelszónak legalább 8 karakter hosszúnak kell lennie", - "password_listed": "Ez a jelszó a világ egyik leggyakrabban használt jelszava. Kérjük, válasszon egy kicsit egyediabbat.", - "password_not_match": "A jelszavak nem egyeznek", - "password_changed_error": "Nem sikerült megváltoztatni a jelszót", - "password_changed": "A jelszó megváltozott", - "logout": "Kijelentkezés", - "login": "Belépés", - "confirm": "megerősít", - "new_password": "Új jelszó", - "current_password": "Jelenlegi jelszó", - "edit": "Ezerkesztése", - "change_password": "Jelszó módosítása", - "cancel": "Megszünteti", - "ok": "Rendben", - "add_forward": "Adjon hozzá egy e-mail továbbító címet", - "add_mail": "Adjon hozzá egy e-mail álnevet", - "new_forward": "newforward@myforeigndomain.org", - "new_mail": "newmail@mydomain.org", - "mail_forward": "E-mail továbbítási cím", - "mail_addresses": "Email címek", - "fullname": "Teljes név", - "password": "Jelszó", - "username": "Felhasználónév", - "information": "Az Ön adata", - "portal": "YunoHost portál" -} diff --git a/portal/locales/id.json b/portal/locales/id.json deleted file mode 100644 index 9109dce..0000000 --- a/portal/locales/id.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "cancel": "Batal", - "portal": "Portal YunoHost", - "information": "Info Anda", - "username": "Nama Pengguna", - "password": "Kata sandi", - "fullname": "Nama Lengkap", - "mail_addresses": "Alamat surel", - "mail_forward": "Alamat surel terusan", - "new_mail": "surelbaru@domainku.org", - "new_forward": "terusanbaru@domainlainku.org", - "add_mail": "Buat surel alias", - "add_forward": "Buat alamat surel terusan", - "ok": "Oke", - "change_password": "Ubah kata sandi", - "edit": "Sunting", - "current_password": "Kata sandi saat ini", - "new_password": "Kata sandi baru", - "confirm": "Konfirmasi", - "login": "Masuk", - "logout": "Keluar", - "password_changed": "Kata sandi diubah", - "password_changed_error": "Tidak dapat mengubah kata sandi", - "password_not_match": "Kata sandi tidak sama", - "password_listed": "Kata sandi ini merupakan salah satu kata sandi yang paling sering digunakan di dunia. Coba pilih sesuatu yang lebih unik.", - "password_too_simple_1": "Panjang kata sandi harus paling tidak 8 karakter", - "wrong_current_password": "Kata sandi saat ini salah", - "invalid_mail": "Alamat surel tidak valid", - "mail_already_used": "Alamat surel sudah digunakan", - "information_updated": "Info diperbarui", - "user_saving_fail": "Tidak dapat menyimpan info baru pengguna", - "wrong_username_password": "Nama pengguna atau kata sandi salah", - "logged_out": "Berhasil keluar", - "please_login": "Masuk untuk mengakses konten ini", - "please_login_from_portal": "Silakan masuk dari portal", - "redirection_error_invalid_url": "Kesalahan pengalihan: URL tidak valid", - "redirection_error_unmanaged_domain": "Kesalahan pengalihan: Domain tak dikelola", - "footerlink_edit": "Sunting profil saya", - "footerlink_documentation": "Dokumentasi", - "footerlink_support": "Dukungan", - "footerlink_administration": "Administrasi", - "password_too_simple_2": "Kata sandi harus sekurang-kurangnya 8 karakter dan memiliki angka, huruf kapital dan huruf kecil", - "password_too_simple_3": "Kata sandi harus sekurang-kurangnya 8 karakter dan memiliki angka, huruf kapital, huruf kecil, dan karakter spesial", - "password_too_simple_4": "Kata sandi harus sekurang-kurangnya 12 karakter dan memiliki angka, huruf kapital, huruf kecil, dan karakter spesial", - "good_practices_about_user_password": "Pilih kata sandi sekurang-kurangnya 8 karakter - meskipun memang adalah hal yang baik jika menggunakan yang lebih panjang (cth. parafrasa) dan/atau menggunakan berbagai macam karakter (kapital, huruf kecil, angka, dan karakter lainnya).", - "invalid_domain": "Domain tidak valid di", - "invalid_mailforward": "Alamat surel terusan tidak valid", - "missing_required_fields": "Isi bidang yang diperlukan" -} diff --git a/portal/locales/it.json b/portal/locales/it.json deleted file mode 100644 index da6a380..0000000 --- a/portal/locales/it.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "add_forward": "Aggiungi un indirizzo di inoltro e-mail", - "add_mail": "Aggiungi un alias email", - "cancel": "Annulla", - "change_password": "Cambia password", - "confirm": "Conferma", - "current_password": "Password attuale", - "edit": "Modifica", - "footerlink_administration": "Amministrazione", - "footerlink_documentation": "Documentazione", - "footerlink_edit": "Modifica il mio profilo", - "footerlink_support": "Supporto", - "fullname": "Nome e cognome", - "information": "Le tue informazioni", - "information_updated": "Informazioni aggiornate", - "invalid_domain": "Dominio non valido in", - "invalid_mail": "Indirizzo email non valido", - "invalid_mailforward": "Indirizzo di inoltro e-mail non valido", - "logged_out": "Disconnesso", - "login": "Accedi", - "logout": "Esci", - "mail_addresses": "Indirizzi email", - "mail_already_used": "Indirizzo email già in uso", - "mail_forward": "Indirizzo di inoltro e-mail", - "missing_required_fields": "Compila i campi richiesti", - "new_forward": "nuovoinoltro@miodominiodifferente.org", - "new_mail": "nuovaemail@miodominio.org", - "new_password": "Nuova password", - "ok": "OK", - "password": "Password", - "password_changed": "Password cambiata", - "password_changed_error": "Impossibile cambiare la password", - "password_not_match": "Le password non corrispondono", - "please_login": "Per favore, accedi per visualizzare il contenuto", - "please_login_from_portal": "Per favore, accedi dal portale", - "portal": "Portale YunoHost", - "user_saving_fail": "Impossibile salvare le informazioni sul nuovo utente", - "username": "Nome utente", - "wrong_current_password": "La password attuale è sbagliata", - "wrong_username_password": "Nome utente o password sbagliati", - "redirection_error_invalid_url": "Errore di reindirizzamento: URL non valido", - "redirection_error_unmanaged_domain": "Errore di redirezionamento: dominio non gestito", - "password_listed": "Questa password è tra le password più utilizzate al mondo. Scegli qualcosa di un po 'più unico.", - "password_too_simple_1": "La password deve contenere almeno 8 caratteri", - "password_too_simple_2": "La password deve contenere almeno 8 caratteri e contiene cifre, caratteri superiori e inferiori", - "password_too_simple_3": "La password deve contenere almeno 8 caratteri e contiene caratteri numerici, superiori, inferiori e speciali", - "password_too_simple_4": "La password deve contenere almeno 12 caratteri e contiene caratteri numerici, superiori, inferiori e speciali", - "good_practices_about_user_password": "Scegli una password utente di almeno 8 caratteri, anche se è buona norma utilizzare quelli più lunghi (ad esempio una passphrase) e / o utilizzare vari tipi di caratteri (lettere maiuscole, minuscole, cifre e caratteri speciali)." -} diff --git a/portal/locales/ja.json b/portal/locales/ja.json deleted file mode 100644 index 1cc0624..0000000 --- a/portal/locales/ja.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "portal": "YunoHost ポータル", - "information": "あなたの情報", - "username": "ユーザー名", - "password": "パスワード", - "fullname": "フルネーム", - "mail_addresses": "電子メールアドレス", - "mail_forward": "電子メール転送アドレス", - "new_mail": "newmail@mydomain.org", - "add_mail": "電子メール エイリアスを追加", - "add_forward": "電子メール転送アドレスを追加", - "ok": "OK", - "change_password": "パスワード変更", - "edit": "編集", - "new_password": "新しいパスワード", - "confirm": "確認", - "logout": "ログアウト", - "password_changed": "パスワードが変更されました", - "password_not_match": "パスワードが一致しません", - "password_too_simple_1": "パスワードは8文字以上である必要があります", - "password_too_simple_2": "パスワードは8文字以上で、数字/大文字/小文字の全てを含む必要があります", - "password_too_simple_3": "パスワードは8文字以上で、数字/大文字/小文字/特殊文字の全てを含む必要があります", - "password_too_simple_4": "パスワードは12文字以上で、数字/大文字/小文字/特殊文字の全てを含む必要があります", - "wrong_current_password": "現在のパスワードが間違っています", - "invalid_mail": "不正な電子メールアドレス", - "invalid_domain": "不正なドメイン", - "invalid_mailforward": "不正な電子メール転送アドレス", - "mail_already_used": "電子メールアドレスは既に使われています", - "information_updated": "情報が更新されました", - "user_saving_fail": "新しいユーザー情報を保存できませんでした", - "missing_required_fields": "必須フィールドに入力してください", - "wrong_username_password": "ユーザー名かパスワードが間違っています", - "logged_out": "ログアウトしました", - "please_login": "このコンテンツにアクセスするにはログインしてください", - "please_login_from_portal": "ポータルからログインしてください", - "redirection_error_invalid_url": "リダイレクションエラー: 不正なURL", - "redirection_error_unmanaged_domain": "リダイレクションエラー: 管理されていないドメイン", - "footerlink_edit": "プロフィールを編集する", - "footerlink_documentation": "ドキュメント", - "footerlink_support": "サポート", - "footerlink_administration": "管理", - "cancel": "キャンセル", - "new_forward": "newforward@myforeigndomain.org", - "current_password": "現在のパスワード", - "login": "ログイン", - "password_changed_error": "パスワードは変更できませんでした", - "password_listed": "このパスワードは世界で最も使われているパスワードのひとつです。もう少しユニークなものを選んでください。", - "good_practices_about_user_password": "ユーザーパスワードは最低でも8文字、より長いもの(パスフレーズなど)にしたり、さまざまな種類の文字(大文字、小文字、数字、特殊文字)を使うことが望ましいです。" -} diff --git a/portal/locales/kab.json b/portal/locales/kab.json deleted file mode 100644 index 99b1680..0000000 --- a/portal/locales/kab.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "username": "Nom d'utilisateur", - "password": "Awal n uɛeddi", - "fullname": "Isem inek ummid", - "ok": "Ih", - "cancel": "Sefsex", - "change_password": "Beddel awal n uffir", - "edit": "Édition", - "current_password": "Awal n uɛeddi amiran", - "new_password": "Awal uffir amaynut", - "confirm": "Sentem", - "login": "Qqen", - "logout": "Senser", - "logged_out": "Yeffeɣ", - "footerlink_documentation": "Tasemlit", - "footerlink_support": "Tallalt", - "footerlink_administration": "Tadbelt" -} diff --git a/portal/locales/lt.json b/portal/locales/lt.json deleted file mode 100644 index 0967ef4..0000000 --- a/portal/locales/lt.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/portal/locales/mk.json b/portal/locales/mk.json deleted file mode 100644 index 0967ef4..0000000 --- a/portal/locales/mk.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/portal/locales/nb_NO.json b/portal/locales/nb_NO.json deleted file mode 100644 index a9946e9..0000000 --- a/portal/locales/nb_NO.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "footerlink_administration": "Administrasjon", - "footerlink_support": "Støtte", - "footerlink_documentation": "Dokumentasjon", - "footerlink_edit": "Rediger min profil", - "redirection_error_unmanaged_domain": "Videresendingsfeil: Uhåndtert domene", - "redirection_error_invalid_url": "Videresendingsfeil: Ugyldig nettadresse", - "please_login_from_portal": "Logg inn fra portalen", - "please_login": "Logg inn for å få tilgang til dette innholdet", - "logged_out": "Utlogget", - "wrong_username_password": "Feil brukernavn eller passord", - "information_updated": "Info oppdatert", - "invalid_domain": "Ugyldig domene i", - "wrong_current_password": "Nåværende passord er feil", - "password_changed": "Passord endret", - "logout": "Logg ut", - "login": "Logg inn", - "confirm": "Bekreft", - "new_password": "Nytt passord", - "current_password": "Nåværende passord", - "edit": "Rediger", - "change_password": "Endre passord", - "cancel": "Avbryt", - "ok": "OK", - "password": "Passord", - "username": "Brukernavn", - "information": "Din informasjon", - "portal": "YunoHost-portal" -} diff --git a/portal/locales/ne.json b/portal/locales/ne.json deleted file mode 100644 index 3f64056..0000000 --- a/portal/locales/ne.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "footerlink_administration": "प्रशासन", - "footerlink_support": "समर्थन", - "footerlink_documentation": "कागजात", - "footerlink_edit": "मेरो प्रोफाइल सम्पादन गर्नुहोस्", - "redirection_error_unmanaged_domain": "पुनर्निर्देशन त्रुटि: अव्यवस्थित डोमेन", - "redirection_error_invalid_url": "रिडिरेसन त्रुटि: अवैध URL", - "please_login_from_portal": "कृपया पोर्टलबाट लग ईन गर्नुहोस्", - "please_login": "यस सामग्री पहुँच गर्न कृपया लग इन गर्नुहोस्", - "logged_out": "लग आउट", - "wrong_username_password": "गलत प्रयोगकर्ता नाम वा पासवर्ड", - "missing_required_fields": "आवश्यक फिल्डहरू भर्नुहोस्", - "user_saving_fail": "नयाँ प्रयोगकर्ता जानकारी बचत गर्न सकेन", - "information_updated": "जानकारी अपडेट गरियो", - "mail_already_used": "इ-मेल ठेगाना पहिले नै प्रयोगमा छ", - "invalid_mailforward": "अवैध ईमेल फर्वार्डिंग ठेगाना", - "invalid_domain": "अमान्य डोमेन भित्र", - "invalid_mail": "अवैध ईमेल ठेगाना", - "wrong_current_password": "हालको पासवर्ड गलत छ", - "good_practices_about_user_password": "कम्तिमा characters क्यारेक्टरहरूको प्रयोगकर्ता पासवर्ड छान्नुहोस् - यद्यपि यो लामो अभ्यास (अर्थात पासफ्रेज) प्रयोग गर्न राम्रो अभ्यास हो र / वा विभिन्न प्रकारका वर्णहरू (अपरकेस, लोअरकेस, अंक र विशेष क्यारेक्टर) प्रयोग गर्नुहोस्।", - "password_too_simple_4": "पासवर्ड कम्तिमा १२ वर्ण लामो हुनु पर्छ र अंक, माथिल्लो, तल्लो र विशेष क्यारेक्टर समावेश गर्दछ", - "password_too_simple_3": "पासवर्ड कम्तिमा characters वर्ण लामो हुनु पर्छ र अंक, माथिल्लो, तल्लो र विशेष क्यारेक्टर समावेश गर्दछ", - "password_too_simple_2": "पासवर्ड कम्तिमा characters क्यारेक्टर लामो हुनुपर्दछ र अंक, माथिल्लो र तल्लो वर्णहरू समावेश गर्दछ", - "password_too_simple_1": "पासवर्ड कम्तिमा characters अक्षर लामो हुनु आवश्यक छ", - "password_listed": "यो पासवर्ड विश्व मा सबै भन्दा बढी प्रयोग भएको पासवर्ड बीच हो। कृपया केहि अलि बढी अनौंठो छनौट गर्नुहोस्।", - "password_not_match": "पासवर्ड मेल खाँदैन", - "password_changed_error": "पासवर्ड परिवर्तन गर्न सकेन", - "password_changed": "पासवर्ड परिवर्तन भयो", - "logout": "बाहिर निस्कनु", - "login": "लग - इन", - "confirm": "पुष्टि गर्नुहोस्", - "new_password": "नया पासवर्ड", - "current_password": "वर्तमान पासवर्ड", - "edit": "सम्पादन गर्नुहोस्", - "change_password": "पासवर्ड परिवर्तन गर्नुहोस्", - "cancel": "रद्द गर्नुहोस्", - "ok": "ठिक छ", - "add_forward": "एक ईमेल अग्रेषण ठेगाना जोड्नुहोस्", - "add_mail": "ईमेल उपनाम थप्नुहोस्", - "new_forward": "नयाँअगाडी@माईफोरिगेन्डोमाइन.org", - "new_mail": "नयाँमेल@माईडोमेन.org", - "mail_forward": "इ-मेल फर्वार्डिंग ठेगाना", - "mail_addresses": "इ-मेल ठेगानाहरू", - "fullname": "पुरा नाम", - "password": "पासवर्ड", - "username": "प्रयोगकर्ता नाम", - "information": "तपाईको जानकारी", - "portal": "YunoHost पोर्टल" -} diff --git a/portal/locales/nl.json b/portal/locales/nl.json deleted file mode 100644 index 5d8607c..0000000 --- a/portal/locales/nl.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "add_forward": "Voeg een e-mail doorstuuradres toe", - "add_mail": "Voeg een e-mailalias toe", - "cancel": "Annuleren", - "change_password": "Verander wachtwoord", - "confirm": "Bevestig", - "current_password": "Huidig wachtwoord", - "edit": "Bewerken", - "footerlink_administration": "Administratie", - "footerlink_documentation": "Documentatie", - "footerlink_edit": "Bewerk mijn profiel", - "footerlink_support": "Ondersteuning", - "fullname": "Voor- en achternaam", - "information": "Uw gegevens", - "information_updated": "Informatie bijgewerkt", - "invalid_domain": "Ongeldig domein in", - "invalid_mail": "Ongeldig e-mailadres", - "invalid_mailforward": "Ongeldig email-doorstuuradres", - "logged_out": "Uitgelogd", - "login": "Inloggen", - "logout": "Uitloggen", - "mail_addresses": "E-mailadressen", - "mail_already_used": "E-mailadres al in gebruik", - "mail_forward": "E-mail doorstuuradres", - "missing_required_fields": "De verplichte velden moeten ingevuld worden", - "new_forward": "nieuw_doorstuuradres@mijndomein.org", - "new_mail": "nieuwe_email@mijndomein.org", - "new_password": "Nieuw wachtwoord", - "ok": "OK", - "password": "Wachtwoord", - "password_changed": "Wachtwoord veranderd", - "password_changed_error": "Kon wachtwoord niet veranderen", - "password_not_match": "De wachtwoorden komen niet overeen", - "please_login": "Log in om toegang te krijgen tot deze inhoud", - "please_login_from_portal": "Log in vanaf het portaal", - "portal": "YunoHost Portaal", - "user_saving_fail": "De nieuwe gebruikersinformatie kon niet opgeslagen worden", - "username": "Gebruikersnaam", - "wrong_current_password": "Het huidige wachtwoord is fout", - "wrong_username_password": "Verkeerde gebruikersnaam of wachtwoord", - "password_too_simple_2": "Het wachtwoord moet minimaal 8 tekens lang zijn en moet cijfers, hoofdletters en kleine letters bevatten", - "password_too_simple_1": "Het wachtwoord moet minimaal 8 tekens lang zijn", - "password_listed": "Dit wachtwoord is een van de meest gebruikte wachtwoorden ter wereld. Kies alstublieft iets wat minder voor de hand ligt.", - "redirection_error_unmanaged_domain": "Omleidingsfout: onbeheerd domein", - "redirection_error_invalid_url": "Omleidingsfout: ongeldige URL", - "good_practices_about_user_password": "Kies een gebruikerswachtwoord van minimaal 8 tekens - hoewel het een goede gewoonte is om langere (bijvoorbeeld een wachtwoordzin) te gebruiken en/of verschillende soorten tekens te gebruiken (hoofdletters, kleine letters, cijfers en speciale tekens).", - "password_too_simple_4": "Het wachtwoord moet minimaal 12 tekens lang zijn en moet cijfers, hoofdletters, kleine letters en speciale tekens bevatten", - "password_too_simple_3": "Het wachtwoord moet minimaal 8 tekens lang zijn en moet cijfers, hoofdletters, kleine letters en speciale tekens bevatten" -} diff --git a/portal/locales/oc.json b/portal/locales/oc.json deleted file mode 100644 index 1b45cf2..0000000 --- a/portal/locales/oc.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "portal": "Portal YunoHost", - "information": "Vòstras informacions", - "username": "Nom d’utilizaire", - "password": "Senhal", - "fullname": "Nom complèt", - "mail_addresses": "Adreça de corrièl", - "mail_forward": "Adreças de transferiment", - "new_mail": "novela_adreça@domeni.org", - "new_forward": "novel_transferiment@domenialonhat.org", - "add_mail": "Ajustar un alias d’adreça electronica", - "add_forward": "Ajustar una adreça de transferiment", - "ok": "OK", - "cancel": "Anullar", - "change_password": "Cambiar lo senhal", - "edit": "Editar", - "current_password": "Senhal actual", - "new_password": "Nòu senhal", - "confirm": "Confirmar", - "login": "Connexion", - "logout": "Desconnexion", - "password_changed": "Senhal modificat", - "password_changed_error": "Una error s’es producha en cambiar lo senhal", - "password_not_match": "Los nòus senhals correspondon pas", - "wrong_current_password": "Lo senhal actual es incorrècte", - "invalid_mail": "Adreça de corrièl invalida", - "invalid_domain": "Nom de domeni invalid dins", - "invalid_mailforward": "Adreça de transferiment invalida", - "mail_already_used": "Adreça ja utilizada", - "information_updated": "Informacions actualizadas", - "user_saving_fail": "Enregistrament impossible de las nòvas informacions utilizaire", - "missing_required_fields": "Garnissètz los camps requesits", - "wrong_username_password": "Nom d’utilizaire o senhal incorrècte", - "logged_out": "Desconnectat", - "please_login": "Mercé de vos identificar per accedir a la pagina", - "please_login_from_portal": "Mercés de vos identificar dins del portal", - "redirection_error_invalid_url": "Error de redireccion : URL invalida", - "redirection_error_unmanaged_domain": "Error de redireccion : domeni pas gerit", - "footerlink_edit": "Editar lo perfil", - "footerlink_documentation": "Documentacion", - "footerlink_support": "Assisténcia", - "footerlink_administration": "Administracion", - "password_listed": "Aqueste senhal es un dels mai utilizats al monde. Se vos plai utilizatz-ne un mai unic.", - "password_too_simple_1": "Lo senhal deu conténer almens 8 caractèrs", - "password_too_simple_2": "Lo senhal deu conténer almens 8 caractèrs e nombres, majusculas e minusculas", - "password_too_simple_3": "Lo senhal deu conténer almens 8 caractèrs e nombres, majusculas e minusculas e caractèrs especials", - "password_too_simple_4": "Lo senhal deu conténer almens 12 caractèrs, de nombre, majusculas, minusculas e caractèrs especials", - "good_practices_about_user_password": "Causissètz un senhal d’almens 8 caractèrs, es de bon far d’utilizar un senhal mai long (es a dire una frasa de senhal) e/o utilizar mantun tipe de caractèrs (majusculas, minusculas, nombres e caractèrs especials)." -} diff --git a/portal/locales/pl.json b/portal/locales/pl.json deleted file mode 100644 index 3acab8d..0000000 --- a/portal/locales/pl.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "add_mail": "Dodaj alias e-mail", - "cancel": "Anuluj", - "change_password": "Zmień hasło", - "confirm": "Potwierdź", - "current_password": "Aktualne hasło", - "edit": "Edytuj", - "footerlink_administration": "Panel administracyjny", - "footerlink_documentation": "Dokumentacja", - "footerlink_edit": "Edytuj mój profil", - "footerlink_support": "Pomoc techniczna", - "fullname": "Pełne imię i nazwisko", - "information": "Twoje informacje", - "logged_out": "Wylogowano", - "login": "Zaloguj Się", - "logout": "Wyloguj", - "mail_addresses": "Adresy e-mail", - "mail_already_used": "Adres e mailowy jest już używany", - "new_forward": "newforward@myforeigndomain.org", - "new_mail": "nowymail@domena.org", - "new_password": "Nowe hasło", - "ok": "OK", - "password": "Hasło", - "password_changed": "Hasło zostało zmienione", - "please_login": "Proszę się zalogować by uzyskać dostęp do tej strony", - "portal": "Portal YunoHost", - "username": "Nazwa użytkownika", - "wrong_username_password": "Zła nazwa użytkownika lub hasło", - "redirection_error_unmanaged_domain": "Błąd przekierowania: domena niezarządzana", - "redirection_error_invalid_url": "Błąd przekierowania: nieprawidłowy adres URL", - "please_login_from_portal": "Zaloguj się z portalu", - "missing_required_fields": "Wypełnij wymagane pola", - "user_saving_fail": "Nie można zapisać nowych informacji o użytkowniku", - "information_updated": "Informacje zaktualizowane", - "invalid_mailforward": "Nieprawidłowy adres e-mail do przekazania", - "invalid_domain": "Nieprawidłowa domena w", - "invalid_mail": "Niepoprawny adres email", - "wrong_current_password": "Obecne hasło jest nieprawidłowe", - "good_practices_about_user_password": "Wybierz hasło użytkownika składające się z co najmniej 8 znaków — chociaż dobrą praktyką jest używanie dłuższych i / lub stosowanie różnego rodzaju znaków (wielkie i małe litery, cyfry i znaki specjalne).", - "password_too_simple_4": "Hasło musi mieć co najmniej 12 znaków i zawierać cyfrę, duże i małe litery oraz znaki specjalne", - "password_too_simple_3": "Hasło musi mieć co najmniej 8 znaków i zawierać cyfrę, duże i małe litery oraz znaki specjalne", - "password_too_simple_2": "Hasło musi mieć co najmniej 8 znaków i zawierać cyfrę, górny i dolny znak", - "password_too_simple_1": "Hasło musi mieć co najmniej 8 znaków", - "password_listed": "To hasło jest jednym z najczęściej używanych haseł na świecie. Wybierz coś bardziej wyjątkowego.", - "password_not_match": "Hasła się nie zgadzają", - "password_changed_error": "Nie można zmienić hasła", - "add_forward": "Dodaj adres e-mail do przekazywania", - "mail_forward": "Adres do przekazywania wiadomości e-mail" -} diff --git a/portal/locales/pt.json b/portal/locales/pt.json deleted file mode 100644 index 63230fa..0000000 --- a/portal/locales/pt.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "add_forward": "Adicionar um endereço de encaminhamento de email", - "add_mail": "Adicionar um alias de email", - "cancel": "Cancelar", - "change_password": "Alterar senha", - "confirm": "Confirmar", - "current_password": "Senha atual", - "edit": "Editar", - "footerlink_administration": "Administração", - "footerlink_documentation": "Documentação", - "footerlink_edit": "Editar o meu perfil", - "footerlink_support": "Suporte", - "fullname": "Nome completo", - "information": "Suas informações", - "information_updated": "Informações atualizadas", - "invalid_domain": "Domínio inválido em", - "invalid_mail": "Endereço de email invalido", - "invalid_mailforward": "Endereço de encaminhamento de email inválido", - "logged_out": "Sessão terminada", - "login": "Entrar", - "logout": "Sair", - "mail_addresses": "Endereço de e-mail", - "mail_already_used": "Endereço de email já está em uso", - "mail_forward": "Endereço de encaminhamento de email", - "missing_required_fields": "Preencha os campos obrigatórios", - "new_forward": "novoreenvio@dominioexterno.org", - "new_mail": "novomail@meudominio.org", - "new_password": "Nova senha", - "ok": "Confirmar", - "password": "Senha", - "password_changed": "Senha alterada", - "password_changed_error": "Não foi possível alterar a senha", - "password_not_match": "As senhas não correspondem", - "please_login": "Por favor inicie sessão para aceder a este conteúdo", - "please_login_from_portal": "Por favor inicie sessão no portal", - "portal": "Portal YunoHost", - "user_saving_fail": "Não foi possível salvar as novas informações do usuário", - "username": "Nome de utilizador", - "wrong_current_password": "A senha atual está incorreta", - "wrong_username_password": "Nome de utilizador e senha errados", - "redirection_error_invalid_url": "Erro de redirecionamento: URL inválido", - "redirection_error_unmanaged_domain": "Erro de redirecionamento: Dominio não gerenciado", - "good_practices_about_user_password": "Escolha uma senha de usuário com pelo menos 8 caracteres - embora seja uma boa prática usar palavras mais longas (ou seja, uma senha) e/ou usar vários tipos de caracteres (maiúsculas, minúsculas, dígitos e caracteres especiais).", - "password_too_simple_4": "A senha precisa ter pelo menos 12 caracteres e conter dígitos, caracteres superior, inferior e caracteres especiais", - "password_too_simple_3": "A senha precisa ter pelo menos 8 caracteres e conter dígitos, caracteres superior, inferior e caracteres especiais", - "password_too_simple_2": "A senha precisa ter pelo menos 8 caracteres e conter dígitos, caracteres superior e inferior", - "password_too_simple_1": "A senha precisa ter pelo menos 8 caracteres", - "password_listed": "Essa senha está entre as senhas mais usadas no mundo. Por favor, escolha algo um pouco mais exclusivo." -} diff --git a/portal/locales/pt_BR.json b/portal/locales/pt_BR.json deleted file mode 100644 index 0967ef4..0000000 --- a/portal/locales/pt_BR.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/portal/locales/ru.json b/portal/locales/ru.json deleted file mode 100644 index 8b9f2e3..0000000 --- a/portal/locales/ru.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "portal": "Портал YunoHost", - "information": "Ваша информация", - "username": "Имя пользователя", - "password": "Пароль", - "fullname": "Полное имя", - "mail_addresses": "Адрес электронной почты", - "ok": "ОК", - "cancel": "Отмена", - "change_password": "Сменить пароль", - "edit": "Редактировать", - "current_password": "Действующий пароль", - "new_password": "Новый пароль", - "confirm": "Подтвердить", - "login": "Авторизоваться", - "logout": "Выйти", - "password_changed": "Пароль изменён", - "password_changed_error": "Не удалось сменить пароль", - "invalid_mail": "Неверный адрес электронной почты", - "invalid_mailforward": "Неверный адрес пересылки электронной почты", - "mail_already_used": "Адрес электронной почты уже используется", - "information_updated": "Информация обновлена", - "user_saving_fail": "Не удалось сохранить информацию о новом пользователе", - "mail_forward": "Адрес пересылки электронной почты", - "new_mail": "newmail@mydomain.org", - "new_forward": "newforward@myforeigndomain.org", - "add_mail": "Добавьте псевдоним электронной почты", - "add_forward": "Добавить адрес пересылки электронной почты", - "password_not_match": "Пароли не совпадают", - "wrong_current_password": "Неверный текущий пароль", - "invalid_domain": "Неправильный домен", - "missing_required_fields": "Заполните обязательные поля", - "wrong_username_password": "Неправильное имя пользователя или пароль", - "logged_out": "Вы вышли из системы", - "please_login": "Пожалуйста, войдите", - "please_login_from_portal": "Пожалуйста, войдите в портал", - "redirection_error_invalid_url": "Ошибка перенаправления: неверный URL", - "redirection_error_unmanaged_domain": "Ошибка перенаправления: неуправляемый домен", - "footerlink_edit": "Редактировать профиль", - "footerlink_documentation": "Документация", - "footerlink_support": "Поддержка", - "footerlink_administration": "Администрирование", - "good_practices_about_user_password": "Выберите пароль пользователя длиной не менее 8 символов, хотя рекомендуется использовать более длинные (например, парольную фразу) и / или использовать символы различного типа (прописные, строчные буквы, цифры и специальные символы).", - "password_too_simple_4": "Пароль должен содержать не менее 12 символов и включать цифры, заглавные и строчные буквы и специальные символы", - "password_too_simple_3": "Пароль должен содержать не менее 8 символов и содержать цифры, заглавные и строчные буквы и специальные символы", - "password_too_simple_2": "Пароль должен содержать не менее 8 символов и включать цифры, заглавные и строчные буквы", - "password_too_simple_1": "Пароль должен быть не менее 8 символов", - "password_listed": "Этот пароль является одним из наиболее часто используемых паролей в мире. Пожалуйста, выберите что-то более уникальное." -} diff --git a/portal/locales/sk.json b/portal/locales/sk.json deleted file mode 100644 index b982bbd..0000000 --- a/portal/locales/sk.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "information": "Vaše údaje", - "username": "Meno používateľa", - "password": "Heslo", - "fullname": "Meno a priezvisko", - "mail_forward": "E-mail pre preposielanie", - "new_mail": "novymail@mojadomena.org", - "new_forward": "novepreposielanie@mojadalsiadomena.org", - "add_mail": "Pridať e-mailovú prezývku/alias", - "add_forward": "Pridať e-mailovú adresu pre preposielanie", - "ok": "OK", - "cancel": "Zrušiť", - "change_password": "Zmeniť heslo", - "edit": "Upraviť", - "current_password": "Aktuálne heslo", - "new_password": "Nové heslo", - "confirm": "Potvrdiť", - "login": "Prihlásiť sa", - "logout": "Odhlásiť sa", - "password_changed": "Heslo bolo zmenené", - "password_changed_error": "Heslo nebolo zmenené", - "password_not_match": "Heslá sa nezhodujú", - "portal": "Portál YunoHost", - "mail_addresses": "E-mailová adresa", - "password_listed": "Toto heslo je jedným z najpoužívanejších na svete. Vyberte, prosím, niečo jedinečnejšie.", - "password_too_simple_1": "Heslo sa musí skladať z aspoň 8 znakov", - "password_too_simple_2": "Heslo musí obsahovať aspoň 8 znakov a musí sa v ňom nachádzať aspoň jedno číslo, veľké a malé písmeno", - "password_too_simple_3": "Heslo musí obsahovať aspoň 8 znakov a musí sa v ňom nachádzať aspoň jedno číslo, veľké, malé písmeno a špeciálny znak", - "wrong_current_password": "Aktuálne heslo je nesprávne", - "invalid_mail": "Neplatná e-mailová adresa", - "invalid_domain": "Neplatná doména v", - "invalid_mailforward": "Neplatná e-mailová adresa pre preposielanie", - "mail_already_used": "Táto e-mailová adresa sa už používa", - "information_updated": "Údaje boli upravené", - "user_saving_fail": "Nepodarilo sa uložiť údaje o používateľovi", - "missing_required_fields": "Vyplňte požadované údaje", - "wrong_username_password": "Chybné meno používateľa alebo heslo", - "logged_out": "Boli ste odhlásený", - "please_login": "Pre zobrazenie obsahu sa, prosím, prihláste", - "please_login_from_portal": "Prosím, prihláste sa z portálu", - "redirection_error_invalid_url": "Chyba presmerovania: Neplatná adresa URL", - "redirection_error_unmanaged_domain": "Chyba presmerovania: Neregistrovaná doména", - "footerlink_edit": "Upraviť môj profil", - "footerlink_documentation": "Dokumentácia", - "footerlink_support": "Podpora", - "footerlink_administration": "Správa", - "password_too_simple_4": "Heslo musí obsahovať aspoň 12 znakov a musí sa v ňom nachádzať aspoň jedno číslo, veľké, malé písmeno a špeciálny znak", - "good_practices_about_user_password": "Vyberte si heslo, ktoré má aspoň 8 znakov - dobrou praxou je však používať dlhšie názvy a kombinovať pri tom rôzne typy znakov (veľké a malé písmená, číslice a špeciálne znaky)." -} diff --git a/portal/locales/sl.json b/portal/locales/sl.json deleted file mode 100644 index 910ba16..0000000 --- a/portal/locales/sl.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cancel": "Prekliči" -} diff --git a/portal/locales/sv.json b/portal/locales/sv.json deleted file mode 100644 index 370bee5..0000000 --- a/portal/locales/sv.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "footerlink_administration": "Administration", - "footerlink_support": "Support", - "footerlink_documentation": "Dokumentation", - "footerlink_edit": "Redigera min profil", - "logged_out": "Utloggad", - "wrong_username_password": "Fel användarnamn eller lösenord", - "missing_required_fields": "Fyll i de obligatoriska fälten", - "user_saving_fail": "Kunde inte spara ny användarinformation", - "information_updated": "Informationen har uppdaterats", - "mail_already_used": "E-postadressen används redan", - "invalid_domain": "Ogiltig domän i", - "invalid_mail": "E-postadressen är ogiltig", - "wrong_current_password": "Det nuvarande lösenordet stämmer inte", - "password_too_simple_4": "Lösenordet måste bestå av minst tolv tecken och innehålla både siffror, små och stora bokstäver samt specialtecken", - "password_too_simple_3": "Lösenordet måste bestå av minst åtta tecken och innehålla både siffror, små och stora bokstäver samt specialtecken", - "password_too_simple_2": "Lösenordet måste bestå av minst åtta tecken och innehålla både siffror, små och stora bokstäver", - "password_too_simple_1": "Lösenordet måste bestå av minst åtta tecken", - "password_listed": "Det här lösenordet är ett av de mest använda i världen. Välj gärna någonting lite mer unikt.", - "password_not_match": "Lösenorden stämmer inte överens", - "password_changed_error": "Kunde inte ändra lösenordet", - "password_changed": "Lösenordet har ändrats", - "logout": "Logga ut", - "login": "Logga in", - "confirm": "Bekräfta", - "new_password": "Nytt lösenord", - "current_password": "Nuvarande lösenord", - "edit": "Redigera", - "change_password": "Byt lösenord", - "cancel": "Avbryt", - "ok": "Ok", - "add_forward": "Lägg till en e-postadress för vidarebefordran", - "add_mail": "Lägg till ett e-postalias", - "new_forward": "ny_vidarebefordring@min_fjarr-doman.org", - "new_mail": "ny_adress@min_doman.org", - "mail_forward": "E-postadress för vidarebefordring", - "mail_addresses": "E-postadresser", - "fullname": "Fullständigt namn", - "password": "Lösenord", - "username": "Användarnamn", - "information": "Din information", - "portal": "YunoHost-portal", - "redirection_error_unmanaged_domain": "Omdirigeringsfel: Okontrollerad domän", - "redirection_error_invalid_url": "Omdirigeringsfel: Ogiltig URL", - "please_login_from_portal": "Logga in från portalen", - "please_login": "Logga in för att få tillgång till det här innehållet", - "invalid_mailforward": "Ogiltig e-post vidarebefordringsadress", - "good_practices_about_user_password": "Välj ett användarlösenord på minst åtta tecken - även om det är bra att använda längre (dvs ett lösenord) och / eller använda olika typer av tecken (versaler, versaler, siffror och specialtecken)." -} diff --git a/portal/locales/te.json b/portal/locales/te.json deleted file mode 100644 index b96d32e..0000000 --- a/portal/locales/te.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "cancel": "రద్దు చేయండి" -} diff --git a/portal/locales/tr.json b/portal/locales/tr.json deleted file mode 100644 index d6e5453..0000000 --- a/portal/locales/tr.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "add_forward": "Bir e-posta yönlendirme adresi ekleyin", - "add_mail": "Bir e-posta takma adı ekleyin", - "cancel": "İptal et", - "change_password": "Parolayı değiştir", - "confirm": "Onayla", - "current_password": "Mevcut parola", - "edit": "Düzenle", - "footerlink_administration": "Yönetim", - "footerlink_documentation": "Belgelendirme", - "footerlink_edit": "Profilimi düzenle", - "footerlink_support": "Destek", - "fullname": "Ad Soyad", - "information": "Bilginiz", - "information_updated": "Bilgi güncellendi", - "invalid_domain": "Geçersiz domain", - "invalid_mail": "Geçersiz e-posta adresi", - "invalid_mailforward": "Geçersiz e-posta iletme adresi", - "logged_out": "Çıkış yapıldı", - "login": "Oturum aç", - "logout": "Çıkış Yap", - "mail_addresses": "E-mail adresleri", - "mail_already_used": "E-posta adresi zaten kullanımda", - "mail_forward": "E-posta yönlendirme adresi", - "missing_required_fields": "Gerekli alanları doldurun", - "new_forward": "newforward@myforeigndomain.org", - "new_mail": "newmail@mydomain.org", - "new_password": "Yeni parola", - "ok": "Tamam", - "password": "Parola", - "password_changed": "şifre değişti", - "password_changed_error": "Şifre değiştirilemedi", - "password_not_match": "Şifreler uyuşmuyor", - "please_login": "Bu içeriğe erişmek için lütfen giriş yapınız", - "please_login_from_portal": "Lütfen portaldan giriş yapınız", - "portal": "YunoHost Portalı", - "user_saving_fail": "Yeni kullanıcı bilgisi kaydedilemedi", - "username": "Kullanıcı adı", - "wrong_current_password": "Geçerli şifre yanlış", - "wrong_username_password": "Yanlış kullanıcı adı veya parola", - "redirection_error_unmanaged_domain": "Yönlendirme hatası: Yönetilmeyen alan", - "redirection_error_invalid_url": "Yönlendirme hatası: Geçersiz URL", - "good_practices_about_user_password": "En az 8 karakterden oluşan bir kullanıcı şifresi seçin - daha uzun olanları (örneğin bir şifre) ve / veya çeşitli karakterleri (büyük harf, küçük harf, rakam ve özel karakterler) kullanmak daha iyidir.", - "password_too_simple_4": "Şifrenin en az 12 karakter uzunluğunda olması ve rakam, büyük ve küçük harfler, özel karakterler içermesi gerekir", - "password_too_simple_3": "Şifrenin en az 8 karakter uzunluğunda olması ve rakam, büyük ve küçük harfler, özel karakterler içermesi gerekir", - "password_too_simple_2": "Şifrenin en az 8 karakter uzunluğunda olması ve rakam, üst ve alt karakterler içermesi gerekir", - "password_too_simple_1": "Şifre en az 8 karakter uzunluğunda olmalı", - "password_listed": "Bu şifre dünyada en çok kullanılan şifreler arasındadır. Lütfen biraz daha benzersiz bir şey seçin." -} diff --git a/portal/locales/uk.json b/portal/locales/uk.json deleted file mode 100644 index 9655a91..0000000 --- a/portal/locales/uk.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "cancel": "Скасувати", - "logged_out": "Ви вийшли з системи", - "footerlink_administration": "Адміністрування", - "footerlink_support": "Підтримка", - "footerlink_documentation": "Документація", - "footerlink_edit": "Редагувати мій профіль", - "redirection_error_unmanaged_domain": "Помилка перенаправлення: Некерований домен", - "redirection_error_invalid_url": "Помилка перенаправлення: Недійсна URL-адреса", - "please_login_from_portal": "Увійдіть у систему з порталу", - "please_login": "Увійдіть, щоб отримати доступ до цього вмісту", - "wrong_username_password": "Неправильне ім'я користувача або пароль", - "missing_required_fields": "Заповніть необхідні поля", - "user_saving_fail": "Не вдалося зберегти нові відомості користувача", - "information_updated": "Відомості оновлено", - "mail_already_used": "Адреса е-пошти вже використовується", - "invalid_mailforward": "Недійсна адреса переадресації е-пошти", - "invalid_domain": "Недійсний домен у", - "invalid_mail": "Недійсна адреса е-пошти", - "wrong_current_password": "Поточний пароль неправильний", - "good_practices_about_user_password": "Виберіть пароль користувача щонайменше 8 символів - хоча це хороша практика використовувати довші (тобто фрази-гасла) та/або використовувати різні символи (великі, малі, числа та спеціальні символи).", - "password_too_simple_4": "Пароль повинен бути щонайменше 12 символів довжиною і містити числа, верхній, нижній регістри та спеціальні символи", - "password_too_simple_3": "Пароль повинен бути щонайменше 8 символів довжиною і містити числа, верхній, нижній регістри та спеціальні символи", - "password_too_simple_2": "Пароль повинен бути щонайменше 8 символів довжиною і містити числа, верхній та нижній регістри", - "password_too_simple_1": "Пароль має складатися не менше ніж з 8 символів", - "password_listed": "Цей пароль є одним з найбільш використовуваних паролів у світі. Будь ласка, виберіть щось трохи більш неповторюване.", - "password_not_match": "Паролі не збігаються", - "password_changed_error": "Не вдалося змінити пароль", - "password_changed": "Пароль змінено", - "logout": "Вийти", - "login": "Увійти", - "confirm": "Підтвердити", - "new_password": "Новий пароль", - "current_password": "Поточний пароль", - "edit": "Редагувати", - "change_password": "Змінити пароль", - "add_forward": "Додайте адресу переадресації е-пошти", - "add_mail": "Додайте аліас е-пошти", - "new_forward": "novapereadresaciya@myforeigndomain.org", - "new_mail": "novaeposhta@mydomain.org", - "mail_forward": "Адреса переадресації е-пошти", - "mail_addresses": "Адреси е-пошти", - "fullname": "Повне ім'я", - "username": "Ім'я користувача", - "information": "Ваші відомості", - "portal": "Портал YunoHost", - "password": "Пароль", - "ok": "Гаразд" -} diff --git a/portal/locales/zh_Hans.json b/portal/locales/zh_Hans.json deleted file mode 100644 index 18eafca..0000000 --- a/portal/locales/zh_Hans.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "footerlink_administration": "管理", - "footerlink_support": "支持", - "footerlink_documentation": "文档", - "footerlink_edit": "编辑我的个人资料", - "redirection_error_unmanaged_domain": "重定向错误:非托管域", - "redirection_error_invalid_url": "重定向错误:无效的 URL", - "please_login_from_portal": "请从门户登录", - "please_login": "请登录以访问此内容", - "logged_out": "登出", - "wrong_username_password": "错误的用户名或密码", - "missing_required_fields": "填写必填项", - "user_saving_fail": "无法保存新的用户信息", - "information_updated": "信息已更新", - "mail_already_used": "电子邮件地址已被使用", - "invalid_mailforward": "无效的电子邮件转发地址", - "invalid_domain": "无效的域", - "invalid_mail": "无效的邮件地址", - "wrong_current_password": "当前密码错误", - "good_practices_about_user_password": "选择至少8个字符的用户密码-尽管使用较长的用户密码(即密码短语)和/或使用各种字符(大写,小写,数字和特殊字符)是一种很好的做法。", - "password_too_simple_4": "密码长度至少为12个字符,并且包含数字,大写,小写和特殊字符", - "password_too_simple_3": "密码长度至少为8个字符,并且包含数字,大写,小写和特殊字符", - "password_too_simple_2": "密码长度至少为8个字符,并且包含数字,大写和小写字符", - "password_too_simple_1": "密码长度至少为8个字符", - "password_listed": "该密码是世界上最常用的密码之一。 请选择一些更独特的东西。", - "password_not_match": "密码不匹配", - "password_changed_error": "无法更改密码", - "password_changed": "密码已更改", - "logout": "登出", - "login": "登录", - "confirm": "确认", - "new_password": "新密码", - "current_password": "当前密码", - "edit": "编辑", - "change_password": "更改密码", - "cancel": "取消", - "ok": "ОК", - "add_forward": "添加电子邮件转发地址", - "add_mail": "添加电子邮件别名", - "new_forward": "新转发@我的外部域.org", - "new_mail": "新邮件@我的域.org", - "mail_forward": "邮件转发地址", - "mail_addresses": "电子邮件地址", - "fullname": "全名", - "password": "密码", - "username": "用户名", - "information": "您的资料", - "portal": "YunoHost 门户" -} diff --git a/portal/login.html b/portal/login.html deleted file mode 100644 index 0b176b9..0000000 --- a/portal/login.html +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/portal/password.html b/portal/password.html deleted file mode 100644 index 9441db0..0000000 --- a/portal/password.html +++ /dev/null @@ -1,38 +0,0 @@ - - -
- -
- {{t_good_practices_about_user_password}} -
- -
- -
-
- - -
-
-
-
- - - -
-
- {{t_cancel}} - -
-
-
-
diff --git a/portal/portal.html b/portal/portal.html deleted file mode 100644 index c95ab04..0000000 --- a/portal/portal.html +++ /dev/null @@ -1,25 +0,0 @@ - - -
- -
diff --git a/vendor/luajwtjitsi/LICENSE b/vendor/luajwtjitsi/LICENSE new file mode 100644 index 0000000..8244556 --- /dev/null +++ b/vendor/luajwtjitsi/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/luajwtjitsi/luajwtjitsi.lua b/vendor/luajwtjitsi/luajwtjitsi.lua new file mode 100644 index 0000000..bbd383b --- /dev/null +++ b/vendor/luajwtjitsi/luajwtjitsi.lua @@ -0,0 +1,259 @@ +local cjson_safe = require 'cjson.safe' +local basexx = require 'basexx' +local digest = require 'openssl.digest' +local hmac = require 'openssl.hmac' +local pkey = require 'openssl.pkey' + +-- Generates an RSA signature of the data. +-- @param data The data to be signed. +-- @param key The private signing key in PEM format. +-- @param algo The digest algorithm to user when generating the signature: sha256, sha384, or sha512. +-- @return The signature or nil and an error message. +local function signRS (data, key, algo) + local privkey = pkey.new(key) + if privkey == nil then + return nil, 'Not a private PEM key' + else + local datadigest = digest.new(algo):update(data) + return privkey:sign(datadigest) + end +end + +-- Verifies an RSA signature on the data. +-- @param data The signed data. +-- @param signature The signature to be verified. +-- @param key The public key of the signer. +-- @param algo The digest algorithm to user when generating the signature: sha256, sha384, or sha512. +-- @return True if the signature is valid, false otherwise. Also returns false if the key is invalid. +local function verifyRS (data, signature, key, algo) + local pubkey = pkey.new(key) + if pubkey == nil then + return false + end + + local datadigest = digest.new(algo):update(data) + return pubkey:verify(signature, datadigest) +end + +local alg_sign = { + ['HS256'] = function(data, key) return hmac.new(key, 'sha256'):final(data) end, + ['HS384'] = function(data, key) return hmac.new(key, 'sha384'):final(data) end, + ['HS512'] = function(data, key) return hmac.new(key, 'sha512'):final(data) end, + ['RS256'] = function(data, key) return signRS(data, key, 'sha256') end, + ['RS384'] = function(data, key) return signRS(data, key, 'sha384') end, + ['RS512'] = function(data, key) return signRS(data, key, 'sha512') end +} + +local alg_verify = { + ['HS256'] = function(data, signature, key) return signature == alg_sign['HS256'](data, key) end, + ['HS384'] = function(data, signature, key) return signature == alg_sign['HS384'](data, key) end, + ['HS512'] = function(data, signature, key) return signature == alg_sign['HS512'](data, key) end, + ['RS256'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha256') end, + ['RS384'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha384') end, + ['RS512'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha512') end +} + +-- Splits a token into segments, separated by '.'. +-- @param token The full token to be split. +-- @return A table of segments. +local function split_token(token) + local segments={} + for str in string.gmatch(token, "([^\\.]+)") do + table.insert(segments, str) + end + return segments +end + +-- Parses a JWT token into it's header, body, and signature. +-- @param token The JWT token to be parsed. +-- @return A JSON header and body represented as a table, and a signature. +local function parse_token(token) + local segments=split_token(token) + if #segments ~= 3 then + return nil, nil, nil, "Invalid token" + end + + local header, err = cjson_safe.decode(basexx.from_url64(segments[1])) + if err then + return nil, nil, nil, "Invalid header" + end + + local body, err = cjson_safe.decode(basexx.from_url64(segments[2])) + if err then + return nil, nil, nil, "Invalid body" + end + + local sig, err = basexx.from_url64(segments[3]) + if err then + return nil, nil, nil, "Invalid signature" + end + + return header, body, sig +end + +-- Removes the signature from a JWT token. +-- @param token A JWT token. +-- @return The token without its signature. +local function strip_signature(token) + local segments=split_token(token) + if #segments ~= 3 then + return nil, nil, nil, "Invalid token" + end + + table.remove(segments) + return table.concat(segments, ".") +end + +-- Verifies that a claim is in a list of allowed claims. Allowed claims can be exact values, or the +-- catch all wildcard '*'. +-- @param claim The claim to be verified. +-- @param acceptedClaims A table of accepted claims. +-- @return True if the claim was allowed, false otherwise. +local function verify_claim(claim, acceptedClaims) + for i, accepted in ipairs(acceptedClaims) do + if accepted == '*' then + return true; + end + if claim == accepted then + return true; + end + end + + return false; +end + +local M = {} + +-- Encodes the data into a signed JWT token. +-- @param data The data the put in the body of the JWT token. +-- @param key The key to use for signing the JWT token. +-- @param alg The signature algorithm to use: HS256, HS384, HS512, RS256, RS384, or RS512. +-- @param header Additional values to put in the JWT header. +-- @param The resulting JWT token, or nil and an error message. +function M.encode(data, key, alg, header) + if type(data) ~= 'table' then return nil, "Argument #1 must be table" end + if type(key) ~= 'string' then return nil, "Argument #2 must be string" end + + alg = alg or "HS256" + + if not alg_sign[alg] then + return nil, "Algorithm not supported" + end + + header = header or {} + + header['typ'] = 'JWT' + header['alg'] = alg + + local headerEncoded, err = cjson_safe.encode(header) + if headerEncoded == nil then + return nil, err + end + + local dataEncoded, err = cjson_safe.encode(data) + if dataEncoded == nil then + return nil, err + end + + local segments = { + basexx.to_url64(headerEncoded), + basexx.to_url64(dataEncoded) + } + + local signing_input = table.concat(segments, ".") + local signature, error = alg_sign[alg](signing_input, key) + if signature == nil then + return nil, error + end + + segments[#segments+1] = basexx.to_url64(signature) + + return table.concat(segments, ".") +end + +-- Verify that the token is valid, and if it is return the decoded JSON payload data. +-- @param token The token to verify. +-- @param expectedAlgo The signature algorithm the caller expects the token to be signed with: +-- HS256, HS384, HS512, RS256, RS384, or RS512. +-- @param key The verification key used for the signature. +-- @param acceptedIssuers Optional table of accepted issuers. If not nil, the 'iss' claim will be +-- checked against this list. +-- @param acceptedAudiences Optional table of accepted audiences. If not nil, the 'aud' claim will +-- be checked against this list. +-- @return A table representing the JSON body of the token, or nil and an error message. +function M.verify(token, expectedAlgo, key, acceptedIssuers, acceptedAudiences) + if type(token) ~= 'string' then return nil, "token argument must be string" end + if type(expectedAlgo) ~= 'string' then return nil, "algorithm argument must be string" end + if type(key) ~= 'string' then return nil, "key argument must be string" end + if acceptedIssuers ~= nil and type(acceptedIssuers) ~= 'table' then + return nil, "acceptedIssuers argument must be table" + end + if acceptedAudiences ~= nil and type(acceptedAudiences) ~= 'table' then + return nil, "acceptedAudiences argument must be table" + end + + if not alg_verify[expectedAlgo] then + return nil, "Algorithm not supported" + end + + local header, body, sig, err = parse_token(token) + if err ~= nil then + return nil, err + end + + -- Validate header + if not header.typ or header.typ ~= "JWT" then + return nil, "Invalid typ" + end + + if not header.alg or header.alg ~= expectedAlgo then + return nil, "Invalid or incorrect alg" + end + + -- Validate signature + if not alg_verify[expectedAlgo](strip_signature(token), sig, key) then + return nil, 'Invalid signature' + end + + -- Validate body + if body.exp and type(body.exp) ~= "number" then + return nil, "exp must be number" + end + + if body.nbf and type(body.nbf) ~= "number" then + return nil, "nbf must be number" + end + + + if body.exp and os.time() >= body.exp then + return nil, "Not acceptable by exp" + end + + if body.nbf and os.time() < body.nbf then + return nil, "Not acceptable by nbf" + end + + if acceptedIssuers ~= nil then + local issClaim = body.iss; + if issClaim == nil then + return nil, "'iss' claim is missing"; + end + if not verify_claim(issClaim, acceptedIssuers) then + return nil, "invalid 'iss' claim"; + end + end + + if acceptedAudiences ~= nil then + local audClaim = body.aud; + if audClaim == nil then + return nil, "'aud' claim is missing"; + end + if not verify_claim(audClaim, acceptedAudiences) then + return nil, "invalid 'aud' claim"; + end + end + + return body +end + +return M