diff --git a/frontend/src/api/types/clients.ts b/frontend/src/api/types/clients.ts index 7ea9419e4..653a5155a 100644 --- a/frontend/src/api/types/clients.ts +++ b/frontend/src/api/types/clients.ts @@ -65,6 +65,8 @@ export interface UpdateClientRequest { backchannel_logout_uri?: string, /// Validation: PATTERN_GROUP restrict_group_prefix?: string, + /// Validation: PATTERN_ATTR + cust_email_mapping?: string, scim?: ScimClientRequestResponse, } @@ -95,6 +97,7 @@ export interface ClientResponse { backchannel_logout_uri?: string, restrict_group_prefix?: string, scim?: ScimClientRequestResponse, + cust_email_mapping?: string, } export interface ClientSecretResponse { diff --git a/frontend/src/api/types/user_attrs.ts b/frontend/src/api/types/user_attrs.ts index dc60a7941..0614b2559 100644 --- a/frontend/src/api/types/user_attrs.ts +++ b/frontend/src/api/types/user_attrs.ts @@ -11,7 +11,6 @@ export interface UserAttrConfigRequest { /// Validation: PATTERN_ATTR_DESC desc?: string, default_value?: string, - /// Currently ignored - will be implemented in a future version typ?: UserAttrConfigTyp, user_editable?: boolean, } @@ -20,7 +19,6 @@ export interface UserAttrConfigValueResponse { name: string, desc?: string, default_value?: string, - /// Currently ignored - will be implemented in a future version typ?: UserAttrConfigTyp, user_editable?: boolean, } diff --git a/frontend/src/i18n/admin/de.ts b/frontend/src/i18n/admin/de.ts index f84299989..9a5418795 100644 --- a/frontend/src/i18n/admin/de.ts +++ b/frontend/src/i18n/admin/de.ts @@ -22,11 +22,14 @@ export let I18nAdminDe: I18nAdmin = { makeEditable: "Editierbar machen", makeEditableP1: "Dieses Attribut kann durch Benutzer editierbar gemacht werden.", makeEditableP2: `ACHTUNG: Diese Änderung kann niemals rückgängig gemacht werden! Jegliche Angaben durch - einen Benutzer direkt sind immer unvalidiert und dürfen NIEMALS für irgengeine Form von Authentifizierung + einen Benutzer direkt sind immer unvalidiert und dürfen NIEMALS für irgengeine Form von Authentifizierung oder Authorisierung genutzt werden!`, makeEditableP3: `Ein Attribut kann deshalb niemals von editierbar zu nicht-editierbar gewandelt werden, weil für eine gewisse Zeit, unabhängig von der Dauer, unvalidierte Eingaben erlaubt waren.`, + addType: "Typ hinzufügen", + removeType: "Typ entfernen", name: "Attribut Name", + typ: "Typ", userEditable: "Durch Benutzer Editierbar", }, backup: { @@ -43,7 +46,7 @@ export let I18nAdminDe: I18nAdmin = { descHsl: `Die folgenden Werte müssen als HSL angegeben werden. Hier wird nur die Basis-Farbe definiert. Alpha Kanäle und andere Werte werden vom Theme dynamisch angepasst.`, descFullCss: `Die folgenden Werte müssen vollständig gültige Angaben für CSS color sein. - Es können auch komplexe Kalkulationen oder die oben definierten CSS Variables + Es können auch komplexe Kalkulationen oder die oben definierten CSS Variables genutzt werden.`, descVariables: `Jede nachfolgende Beschriftung ist gleichzeitig der Name der CSS Variable. Das heisst, dass z.B. die freien Eingaben wiederum die Variablen referenzieren können, z.B. mit @@ -52,6 +55,10 @@ export let I18nAdminDe: I18nAdmin = { confidential: "Vertraulich", confidentialNoSecret: "Dies is kein vertraulicher Client und hat somit kein Secret.", config: "Client Konfiguration", + custEmailMapping: "Custom E-Mail Mapping", + custEmailMappingExplanation: "Use a custom attribute for the E-Mail that will be provided to the client.", + custEmailMappingNoAttrs: `No custom attributes are available for custom E-Mail mapping. + The attribute must be of type \`email\` and must not be user editable.`, delete1: "Soll dieser Client wirklich gelöscht werden?", descAuthCode: `Die Gültigkeit der Auth Codes kann angepasst werden um zusätzliche Sicherheit zu gewinnen. Auth Codes können nur einmalig verwendet werden und sind normalerweise für 60 @@ -62,14 +69,14 @@ export let I18nAdminDe: I18nAdmin = { Er dient lediglich der Anzeige auf der Login Seite.`, descGroupPrefix: `Der Login für diesen Client kann limitiert werden durch ein optionales Gruppen Prefix. Nur Benutzer, die zur entsprechenden Gruppe gehören, dürfen sich bei diesem Client einloggen.`, - descOrigin: `Externe, zusätzlich erlaubte Origins - normalerweise nur notwendig, wenn dieser + descOrigin: `Externe, zusätzlich erlaubte Origins - normalerweise nur notwendig, wenn dieser Client direkt aus dem Browser heraus Requests zu Rauthy machen muss, typischerweise SPAs.`, - descPKCE: `Wenn der Client Support für PKCE hat, sollte zur zusätzlichen Sicherheit immer S256 + descPKCE: `Wenn der Client Support für PKCE hat, sollte zur zusätzlichen Sicherheit immer S256 PKCE aktiviert werden. Wenn ein nicht-vertraulicher Client (z.B. eine SPA) genutzt wird, muss mindestens eine PKCE Challenge aktiviert sein, um ausreichend Sicherheit bieten zu können.`, descPKCEEnforce: `Wenn PKCE aktiviert ist, erzwingt Rauthy die Nutzung and verweigert Logins, die keine korrekte Challenge bereit stellen.`, - descUri: `Es können beliebig viele Redirect URIs angegeben werden. Am Ende einer Jeden wird + descUri: `Es können beliebig viele Redirect URIs angegeben werden. Am Ende einer Jeden wird optional * als Wildcard akzeptiert.`, errConfidentialPKCE: `Der Client muss entweder vertraulich sein oder mindestens eine PKCE Challenge aktiviert haben.`, @@ -77,7 +84,7 @@ export let I18nAdminDe: I18nAdmin = { groupLoginPrefix: "Login Gruppen Prefix", name: "Client Name", scim: { - baseUri: `Die SCIM base URI muss jene sein, von der Sub-Routen wie + baseUri: `Die SCIM base URI muss jene sein, von der Sub-Routen wie {base_uri}/Users/{id} korrekt abgeleitet werden können.`, desc: "Sollte dieser client {{ SCIM_LINK }} unterstützen, kann es hier aktiviert werden.", enable: "SCIMv2 aktivieren", @@ -91,7 +98,7 @@ export let I18nAdminDe: I18nAdmin = { reqLi1: "Der client muss externalId korrekt handhaben.", reqLi2: `Mindestens die /Users endpunkte mit filter=externalId eq "*" und filter=userName eq "*" müssen unterstützt sein.`, - reqLi3: `Wenn Gruppen sychronisiert werden sollen, so müssen unter /Groups zusätzlich + reqLi3: `Wenn Gruppen sychronisiert werden sollen, so müssen unter /Groups zusätzlich filter=displayName eq "*" unterstützt sein.`, }, scopes: { @@ -107,7 +114,7 @@ export let I18nAdminDe: I18nAdmin = { cacheDuration: "Cache Dauer (Stunden)", generate: "Neues Secret Generieren", rotateDesc1: `Um unterbrechungsfreie Updates durchfürhen zu können, ist es möglich, das bestehende Secret - für eine gewisse Zeit im in-memory Cache zu behalten. Es kann ein Wert zwischen 1 und 24 Stunden + für eine gewisse Zeit im in-memory Cache zu behalten. Es kann ein Wert zwischen 1 und 24 Stunden angegeben werden.`, rotateDesc2: "Achtung: Das derzeitige Secret sollte nicht im Cache behalten werden, wenn es ein Leak gab!", }, @@ -151,13 +158,13 @@ export let I18nAdminDe: I18nAdmin = { keysAvailable: "Verfügbare Keys", migrate: "Migrieren", migrateToKey: "Migriere alle Werte zu folgendem Encryption Key", - p1: `Diese Schlüssel werden für die zusätzliche Verschlüsselung in verschiedenen Situationen genutzt, wie + p1: `Diese Schlüssel werden für die zusätzliche Verschlüsselung in verschiedenen Situationen genutzt, wie z.B. gewisse Werte innerhalb der Datenbank oder Session Cookies. Sie sind statisch konfiguriert, aber können als best-practice manuell rotiert werden.`, p2: `Der aktive Schlüssel ist ebenfalls statisch im Rauthy config file gesetzt. Alle neu-verschlüsselten Werte werden mit dem aktiven Schlüssel verschlüsselt, während alte zur Rückwärts-Kompatibilität parallel existieren können.`, - p3: `Das Migrieren aller verschlüsselten Werte an dieser Stelle kann, je nach System, einige Zeit in + p3: `Das Migrieren aller verschlüsselten Werte an dieser Stelle kann, je nach System, einige Zeit in Anspruch nehmen.`, pNotPossible: 'Zur Migration müssen mindestens 2 Encryption Keys vorhanden sein.', }, @@ -166,26 +173,26 @@ export let I18nAdminDe: I18nAdmin = { currValuesHead: 'Derzeitige Werte', currValues1: 'Die derzeitigen im Backend konfigurierten Werte sind die folgenden:', - currValuesNote: `Notiz: Die Login Zeit vom Backend wird nur dann eine gute Richtlinie sein, nachdem - mindestens 5 erfolgreiche Logins seit dem letzten Neustart gemacht wurden. Der Ausgangswert ist immer + currValuesNote: `Notiz: Die Login Zeit vom Backend wird nur dann eine gute Richtlinie sein, nachdem + mindestens 5 erfolgreiche Logins seit dem letzten Neustart gemacht wurden. Der Ausgangswert ist immer 2000 ms und wird mit jedem erfolgreichen Login neu gemittelt.`, currValuesThreadsAccess: 'Threads (p_cost) die Rauthy zur Verfügung stehen', loginTimeHead: 'Ein paar Worte zur Login Zeit', loginTime1: `Generell möchten User alles so schnell wie möglich. Für eine sichere Login Prozedur jedoch sollte mindestens eine Dauer von 500 - 1000 ms anvisiert werden and kein Problem darstellen. Die Zeit zum - Passwort Hashing darf nicht zu kurz gewählt werden, weil dadurch die Stärke des Hashes reduziert werden + Passwort Hashing darf nicht zu kurz gewählt werden, weil dadurch die Stärke des Hashes reduziert werden würde.`, loginTime2: `Um standardmäßig genügend Sicherheit zu gewährleisten, erlaubt dieses Tool keine kleineren Werte als 500 ms für die Login Zeit.`, mCost1: `Die m_cost definiert die Menga an Speicher (in kB) die zum Hashing verwendet - wird. Je höher dieser Wert, umso besser (sicherer), aber die notwendigen Ressourcen müssen natürlich + wird. Je höher dieser Wert, umso besser (sicherer), aber die notwendigen Ressourcen müssen natürlich vorhanden sein.
Wenn z.B. 4 Passwörter zur selben Zeit gehasht werden, wird selbstverständlich 4 x m_cost an Speicher benötigt, was zu jeder Zeit zur Verfügung stehen muss.`, mCost2: `Den "richtigen" Wert für m_cost zu finden ist glücklicherweise sehr einfach. Definiere - das Maximum an Speicher, das Rauthy nutzen sollte, dividiere die Menge durch die Anzahl paralleler Logins, + das Maximum an Speicher, das Rauthy nutzen sollte, dividiere die Menge durch die Anzahl paralleler Logins, die möglich sein sollten (MAX_HASH_THREADS) und ziehe hier von eine gewisse statische Menge ab. Die Höhe des statisch benötigten Speichers hängt von der gewählten Datenbank und Anzahl Benutzer ab, jedoch wird sie in den meisten Fällen im Bereich von 32 - 96 MB sein.`, @@ -196,22 +203,22 @@ export let I18nAdminDe: I18nAdmin = { pCost2: `Die generelle Regel lautet:
Setze p_cost auf den zweifachen Wert der verfügbares CPU Kerne.
Wenn z.B. 4 Kerne zur Verfügung stehen, wäre eine p_cost von 8 ein guter Wert.
- Der Wert muss allerdings die maximale Anzahl parallel erlaubter Logins (MAX_HASH_THREADS) + Der Wert muss allerdings die maximale Anzahl parallel erlaubter Logins (MAX_HASH_THREADS) berücksichtigen und ggf. entsprechend reduziert werden.`, tCost1: `t_cost ist ein Multiplikator für die Zeit fürs Hashing. Dies ist der einzige - Wert, der durch Testen auf der Zielarchitektur gefunden werden muss, weil m_cost und + Wert, der durch Testen auf der Zielarchitektur gefunden werden muss, weil m_cost und p_cost gewissenermaßen vorgegeben sind.`, tCost2: `Das Finden des Wertes ist einfach: Setze m_cost und p_cost wie oben erklärt und erhöhe t_cost so lange, bis die gewünschte Login Zeit erreicht wird.`, utilityHead: 'Parameter Berechnungs-Werkzeug', - utility1: `Das folgende Werkzeug kann zum Finden passender Werte für dieses Rauthy deployment genutzt - werden. Da die Werte von sehr vielen Faktoren abhängen, sollten dieser auf der finalen Architektur - eingestellt werden, am besten zu Zeiten der am höchsten erwarteten Last, um keine zu hohen Werte + utility1: `Das folgende Werkzeug kann zum Finden passender Werte für dieses Rauthy deployment genutzt + werden. Da die Werte von sehr vielen Faktoren abhängen, sollten dieser auf der finalen Architektur + eingestellt werden, am besten zu Zeiten der am höchsten erwarteten Last, um keine zu hohen Werte einzustellen.`, utility2: `m_cost ist Optional und der als minimal sichere Wert von 32768 würde automatisch - gewählt werden. Sollte p_cost ebenfalls nicht gegeben sein, so wird Rauthy die maximal + gewählt werden. Sollte p_cost ebenfalls nicht gegeben sein, so wird Rauthy die maximal verfügbare Menge and Kernen nutzen.`, time: "Zeit", @@ -219,10 +226,10 @@ export let I18nAdminDe: I18nAdmin = { tune: 'Wichtig: Diese Werten müssen auf der finalen Architektur eingestellt werden!', pDetials: `Für eine detailiertere Einführung in den Argon2ID Alrogithmus stehen vielen Quellen online zur Verfügung. Hier werden nur ganz kurz die Werte erklärt. Die folgenden drei Werte müssen konfiguriert werden:`, - pTune: `Die Werte können stark variieren in Abhängigkeit vom System und der generellen Systemlast. Je + pTune: `Die Werte können stark variieren in Abhängigkeit vom System und der generellen Systemlast. Je stärker das System, desto sicherere Werte können gewählt werden.`, pUtility: `Dieses Werkzeug ist eine Hilfe zum Finden der besten Argon2ID Werte für das jeweilige System. - Argon2ID is der derzeit sicherste, verfügbare Passwort Hashing Algorithmus. Um das volle Potential + Argon2ID is der derzeit sicherste, verfügbare Passwort Hashing Algorithmus. Um das volle Potential ausschöpfen zu können, müssen die Werte allerdings auf das System angepasst werden.`, mCost3: "Der minimal erlaubte Wert für m_cost ist 32768." }, @@ -249,8 +256,8 @@ export let I18nAdminDe: I18nAdmin = { jwks: { alg: "Algorithmus", p1: "Dies sind die Json Web Keys (JWKs) die für das Signieren der Tokens genutzt werden.", - p2: `JWKs werden standardmäßig automatisch an jedem 1. des Monats rotiert. Für alle neuen Tokens wird - immer die aktuellste Version eines Keys für den jeweiligen Algorithmus verwerndet. Alte Keys werden für + p2: `JWKs werden standardmäßig automatisch an jedem 1. des Monats rotiert. Für alle neuen Tokens wird + immer die aktuellste Version eines Keys für den jeweiligen Algorithmus verwerndet. Alte Keys werden für eine Weile behalten um bestehende Tokens validieren zu können und nach einer gewissen Zeit automatisch gelöscht.`, p3: `Die Keys können manuell rotiert werden. Abhängig von der Hardware auf der diese Rauthy Instanz läuft, kann dies einige Sekunden in Anspruch nehmen.`, @@ -289,7 +296,7 @@ export let I18nAdminDe: I18nAdmin = { clientName: "Client Name", custRootCa: "Eigenes Root CA PEM", descAuthMethod: `Die Authentication Method, welche für den /token Endpunkt genutzt werden soll. - Die meisten Provider sollten mit basic funktionieren, manche jedoch nur mit + Die meisten Provider sollten mit basic funktionieren, manche jedoch nur mit post. In seltenen Fällen müssen beide Optionen aktiviert werden, auch wenn es gegen das RFC verstößt.`, descClientId: "Client ID, vom Auth Provider vorgegeben.", @@ -365,9 +372,9 @@ export let I18nAdminDe: I18nAdmin = { }, attributes: "Attribute", deleteUser: "Soll dieser Benutzer wirklich gelöscht werden?", - descAttr: `Setze individuelle Benutzer Attribute. Alle Key / Value Paare + descAttr: `Setze individuelle Benutzer Attribute. Alle Key / Value Paare werden als String / JSON Wert gehandhabt.`, - forceLogout: `Sollen sämtliche, für diesen Benutzer existierenden Sessions invalidiert und + forceLogout: `Sollen sämtliche, für diesen Benutzer existierenden Sessions invalidiert und Refresh Tokens gelöscht werden?`, lastLogin: "Letzter Login", manualInitDesc: `Der Benutzer kann jedoch ebenfalls hier initialisiert werden. In diesem Fall muss das diff --git a/frontend/src/i18n/admin/en.ts b/frontend/src/i18n/admin/en.ts index 175629d32..22148892e 100644 --- a/frontend/src/i18n/admin/en.ts +++ b/frontend/src/i18n/admin/en.ts @@ -25,7 +25,10 @@ export let I18nAdminEn: I18nAdmin = { untrusted data and MUST NEVER be used for any form of authentication or authorization!`, makeEditableP3: `An attribute cannot be changed from editable to non-editable, because it allowed untrusted inputs in the past, no matter for how long this was the case.`, + addType: "Add Type", + removeType: "Remove Type", name: "Attribute Name", + typ: "Type", userEditable: "User Editable", }, backup: { @@ -49,6 +52,10 @@ export let I18nAdminEn: I18nAdmin = { confidential: "Confidential", confidentialNoSecret: "This is a non-confidential client and therefore has not secret.", config: "Client Configuration", + custEmailMapping: "Custom E-Mail Mapping", + custEmailMappingExplanation: "Use a custom attribute for the E-Mail that will be provided to the client.", + custEmailMappingNoAttrs: `No custom attributes are available for custom E-Mail mapping. + The attribute must be of type \`email\` and must not be user editable.`, delete1: "Are you sure you want to delete this client?", descAuthCode: `The validity for auth codes can be adjusted for increased security. Auth codes can be used only once and are valid for 60 seconds by default. The shorter the validity, the @@ -61,12 +68,12 @@ export let I18nAdminEn: I18nAdmin = { Only users, that are assigned to a matching group, will be allowed to log in.`, descOrigin: `External, additionally allowed origins - usually only necessary, if this client needs to make requests to Rauthy directly from the browser, typically SPAs.`, - descPKCE: `If the client supports it, you should always activate S256 PKCE for additional - security. If a non-confidential client (e.g. a SPA) is being used, you must at least + descPKCE: `If the client supports it, you should always activate S256 PKCE for additional + security. If a non-confidential client (e.g. a SPA) is being used, you must at least activate one of the PKCE challenges to have enough security.`, descPKCEEnforce: `If any PKCE is activated, Rauthy will enforce the usage during Logins, and rejects login request that do not contain a valida challenge.`, - descUri: `You can provide as many redirect URIs as you like. At the end of each, you can use + descUri: `You can provide as many redirect URIs as you like. At the end of each, you can use * as a Wildcard.`, errConfidentialPKCE: `The client must either be confidential or have at least one PKCE challenge activated.`, @@ -74,7 +81,7 @@ export let I18nAdminEn: I18nAdmin = { groupLoginPrefix: "Login Group Prefix", name: "Client Name", scim: { - baseUri: `The SCIM base URI is the one from which the sub routes like + baseUri: `The SCIM base URI is the one from which the sub routes like {base_uri}/Users/{id} can be derived correctly.`, desc: "If this client supports {{ SCIM_LINK }}, you can activate it here.", enable: "Enable SCIMv2", @@ -88,7 +95,7 @@ export let I18nAdminEn: I18nAdmin = { reqLi1: "The client must handle externalId correctly.", reqLi2: `At least /Users endpoints with filter=externalId eq "*" and filter=userName eq "*" must be supported.`, - reqLi3: `If groups should be synchronized, /Groups must also support + reqLi3: `If groups should be synchronized, /Groups must also support filter=displayName eq "*".`, }, scopes: { @@ -96,7 +103,7 @@ export let I18nAdminEn: I18nAdmin = { default: "Default Scopes", desc: `Allowed Scopes are the ones the client is allowed to request dynamically during a redirect to the login when using the authorization_code flow. The default - scopes will always be added to the tokens to solve some issues when using the + scopes will always be added to the tokens to solve some issues when using the password for instance.`, }, secret: { @@ -146,11 +153,11 @@ export let I18nAdminEn: I18nAdmin = { keysAvailable: "Available Keys", migrate: "Migrate", migrateToKey: 'Migrate all existing encrypted values to the following key', - p1: `These Keys are used for an additional encryption at rest, independently from any data store technology + p1: `These Keys are used for an additional encryption at rest, independently from any data store technology used under the hood. They are configured statically, but can be rotated and migrated on this page manually.`, p2: `The active key is statically set in the Rauthy config file / environment variables. It cannot be changed here dynamically. All new JWK encryption's will always use the currently active key.`, - p3: `If you migrate all existing secrets, it might take a few seconds to finish, if you have a big + p3: `If you migrate all existing secrets, it might take a few seconds to finish, if you have a big dataset.`, pNotPossible: 'To be able to migrate, at least 2 encryption keys need to be available.', }, @@ -159,16 +166,16 @@ export let I18nAdminEn: I18nAdmin = { currValuesHead: 'Current values', currValues1: 'The current values from the backend are the following:', - currValuesNote: `Note: The Login Time from the backend does only provide a good guideline after at least 5 - successful logins, after Rauthy has been started. The base value is always 2000 ms after a fresh restart + currValuesNote: `Note: The Login Time from the backend does only provide a good guideline after at least 5 + successful logins, after Rauthy has been started. The base value is always 2000 ms after a fresh restart and will adjust over time with each successful login.`, currValuesThreadsAccess: 'Threads (p_cost) Rauthy has access to', loginTimeHead: 'A word about Login Time', - loginTime1: `Generally, users want everything as fast as possible. When doing a safe login though, a time - between 500 - 1000 ms should not be a problem. The login time must not be too short, since it would lower + loginTime1: `Generally, users want everything as fast as possible. When doing a safe login though, a time + between 500 - 1000 ms should not be a problem. The login time must not be too short, since it would lower the strength of the hash, of course.`, - loginTime2: `To provide as much safety by default as possible, this utility does not allow you to go below + loginTime2: `To provide as much safety by default as possible, this utility does not allow you to go below 500 ms for the login time.`, mCost1: `The m_cost defines the amount of memory (in kB), which is used for the hashing. @@ -176,12 +183,12 @@ export let I18nAdminEn: I18nAdmin = { When you hash 4 passwords at the same time, for instance, the backend needs 4 x m_cost during the hashing. These resources must be available.`, mCost2: `Tuning m_cost is pretty easy. Define the max amount of memory that Rauthy should use, - divide it by the number of max allowed parallel logins (MAX_HASH_THREADS) and subtract a small + divide it by the number of max allowed parallel logins (MAX_HASH_THREADS) and subtract a small static amount of memory. How much static memory should be taken into account depends on the used database and the total amount of users, but will typically be in the range of 32 - 96 MB.`, mCost3: 'The minimal allowed m_cost is 32768.', - pCost1: `The p_cost defines the amount of parallelism for hashing. This value most often + pCost1: `The p_cost defines the amount of parallelism for hashing. This value most often tops out at ~8, which is the default for Rauthy.`, pCost2: `The general rule is:
Set the p_cost to twice the size of cores your have available.
@@ -189,33 +196,33 @@ export let I18nAdminEn: I18nAdmin = { However, this value must take the configured allowed parallel logins (MAX_HASH_THREADS) into account and be reduced accordingly.`, - tCost1: `The t_cost defines the amount of time for hashing. This value is actually the - only value, that needs tuning, since m_cost and p_cost are basically given by the + tCost1: `The t_cost defines the amount of time for hashing. This value is actually the + only value, that needs tuning, since m_cost and p_cost are basically given by the environment.`, tCost2: `Tuning is easy: Set m_cost and p_cost accordingly and then increase t_cost as long as you have not reached your hashing-time-goal.`, utilityHead: 'Parameter Calculation Utility', - utility1: `You can use this tool to approximate good values for your deployment. Keep in mind, that this - should be executed with Rauthy in its final place with all final resources available. You should execute + utility1: `You can use this tool to approximate good values for your deployment. Keep in mind, that this + should be executed with Rauthy in its final place with all final resources available. You should execute this utility during load to not over tune.`, - utility2: `m_cost is optional and the safe minimal value of 32768 would be chosen, + utility2: `m_cost is optional and the safe minimal value of 32768 would be chosen, if empty. p_cost is optional too and Rauthy will utilize all threads it can see, if empty.`, time: "Time", targetTime: "Target Time", tune: 'Important: These values need to be tuned on the final architecture!', - pDetials: `If you want a detailed introduction to Argon2ID, many sources exist online. This guide just + pDetials: `If you want a detailed introduction to Argon2ID, many sources exist online. This guide just gives very short overview about the values. Three of them need to be configured:`, - pTune: `They change depending on the capabilities of the system. The more powerful the system, the more safe + pTune: `They change depending on the capabilities of the system. The more powerful the system, the more safe these values can be.`, pUtility: `This utility helps you find the best Argon2ID settings for your platform. - Argon2ID is currently the safest available password hashing algorithm. To use it to its fullest potential, + Argon2ID is currently the safest available password hashing algorithm. To use it to its fullest potential, it has to be tuned for each deployment.`, }, openapi: "If you want to integrate an external application and use Rauthy's API, take a look at the", openapiNote: `Depending on the backend configuration, the Swagger UI may not be exposed publicly at this point. - It is however by default available via the internal metrics HTTP server to not expose any + It is however by default available via the internal metrics HTTP server to not expose any information.`, source: "The source code can be found here", }, @@ -239,7 +246,7 @@ export let I18nAdminEn: I18nAdmin = { p1: "These are the Json Web Keys (JWKs) used for token singing.", p2: `The JWKs will be rotated by default every 1st of a month. For all newly created tokens, only the latest available key for the given algorithm will be used for signing. Old keys will be kept for a while to make sure - that currently valid tokens can still be validated properly. After a while, they will be cleaned up + that currently valid tokens can still be validated properly. After a while, they will be cleaned up automatically.`, p3: `Keys can also be rotated manually. Depending on the hardware this Rauthy instance is running on, it might take a few seconds.`, @@ -356,7 +363,7 @@ export let I18nAdminEn: I18nAdmin = { forceLogout: `Are you sure you want to invalidate all existing sessions and delete all refresh tokens for this user?`, lastLogin: "Last Login", - manualInitDesc: `The user can also be initialized here, In this case though, you need to communicate the + manualInitDesc: `The user can also be initialized here, In this case though, you need to communicate the password directly.`, manualInit: "Manual Initialization", mfaDelete1: "You can delete Passkeys for this users.", diff --git a/frontend/src/i18n/admin/interface.ts b/frontend/src/i18n/admin/interface.ts index 1cd7fa135..4f1f13d59 100644 --- a/frontend/src/i18n/admin/interface.ts +++ b/frontend/src/i18n/admin/interface.ts @@ -27,7 +27,10 @@ export interface I18nAdmin { // inserted as html makeEditableP2: string, makeEditableP3: string, + addType: string, + removeType: string, name: string, + typ: string, userEditable: string, }, backup: { @@ -49,6 +52,9 @@ export interface I18nAdmin { confidential: string, confidentialNoSecret: string, config: string, + custEmailMapping: string, + custEmailMappingExplanation: string, + custEmailMappingNoAttrs: string, delete1: string, descAuthCode: string, descClientUri: string, diff --git a/frontend/src/i18n/admin/ko.ts b/frontend/src/i18n/admin/ko.ts index 2c3971c68..b46e92031 100644 --- a/frontend/src/i18n/admin/ko.ts +++ b/frontend/src/i18n/admin/ko.ts @@ -23,7 +23,10 @@ export let I18nAdminKo: I18nAdmin = { untrusted data and MUST NEVER be used for any form of authentication or authorization!`, makeEditableP3: `An attribute cannot be changed from editable to non-editable, because it allowed untrusted inputs in the past, no matter for how long this was the case.`, + addType: "Add Type", + removeType: "Remove Type", name: "속성 이름", + typ: "Type", userEditable: "User Editable", }, backup: { @@ -47,6 +50,10 @@ export let I18nAdminKo: I18nAdmin = { confidential: "기밀", confidentialNoSecret: "이 클라이언트는 기밀이 아닌 클라이언트이므로 Secret이 없습니다.", config: "클라이언트 설정", + custEmailMapping: "Custom E-Mail Mapping", + custEmailMappingExplanation: "Use a custom attribute for the E-Mail that will be provided to the client.", + custEmailMappingNoAttrs: `No custom attributes are available for custom E-Mail mapping. + The attribute must be of type \`email\` and must not be user editable.`, delete1: "이 클라이언트를 삭제하시겠습니까?", descAuthCode: `보안을 강화하기 위해 인증 코드의 유효 기간을 조정할 수 있습니다. 인증 코드는 한 번만 사용할 수 있으며 기본적으로 60초 동안 유효합니다. 클라이언트가 로그인 절차를 @@ -70,7 +77,7 @@ export let I18nAdminKo: I18nAdmin = { groupLoginPrefix: "Login Group Prefix", name: "클라이언트 이름", scim: { - baseUri: `The SCIM base URI is the one from which the sub routes like + baseUri: `The SCIM base URI is the one from which the sub routes like {base_uri}/Users/{id} can be derived correctly.`, desc: "If this client supports {{ SCIM_LINK }}, you can activate it here.", enable: "Enable SCIMv2", @@ -84,7 +91,7 @@ export let I18nAdminKo: I18nAdmin = { reqLi1: "The client must handle externalId correctly.", reqLi2: `At least /Users endpoints with filter=externalId eq "*" and filter=userName eq "*" must be supported.`, - reqLi3: `If groups should be synchronized, /Groups must also support + reqLi3: `If groups should be synchronized, /Groups must also support filter=displayName eq "*".`, }, scopes: { @@ -345,7 +352,7 @@ export let I18nAdminKo: I18nAdmin = { descAttr: `사용자 지정 속성을 설정합니다. 모든 키/값 쌍은 문자열/JSON 값으로 처리됩니다.`, forceLogout: `기존 세션을 모두 삭제하고, 이 사용자의 모든 Refresh 토큰을 삭제하시겠습니까?`, lastLogin: "마지막 로그인", - manualInitDesc: `The user can also be initialized here, In this case though, you need to communicate the + manualInitDesc: `The user can also be initialized here, In this case though, you need to communicate the password directly.`, manualInit: "Manual Initialization", mfaDelete1: "이 사용자의 패스키를 삭제할 수 있습니다.", diff --git a/frontend/src/lib/admin/attrs/AttrConfig.svelte b/frontend/src/lib/admin/attrs/AttrConfig.svelte index 3764411ec..c4802ce9e 100644 --- a/frontend/src/lib/admin/attrs/AttrConfig.svelte +++ b/frontend/src/lib/admin/attrs/AttrConfig.svelte @@ -2,14 +2,16 @@ import Button from "$lib5/button/Button.svelte"; import Input from "$lib5/form/Input.svelte"; import IconCheck from "$icons/IconCheck.svelte"; + import Options from "$lib5/Options.svelte"; import {useI18n} from "$state/i18n.svelte"; import {useI18nAdmin} from "$state/i18n_admin.svelte"; import {fetchPut} from "$api/fetch"; import Form from "$lib5/form/Form.svelte"; - import type {UserAttrConfigRequest, UserAttrConfigValueResponse} from "$api/types/user_attrs.ts"; + import type {UserAttrConfigRequest, UserAttrConfigTyp, UserAttrConfigValueResponse} from "$api/types/user_attrs.ts"; import {PATTERN_ATTR, PATTERN_ATTR_DESC} from "$utils/patterns"; import CheckIcon from "$lib/CheckIcon.svelte"; import {slide} from "svelte/transition"; + import { ATTR_TYPES } from "$utils/constants"; let { attr, @@ -34,8 +36,10 @@ let name = $state(attr.name); let desc = $state(attr.desc); let defaultValue = $state(attr.default_value); + let typ: UserAttrConfigTyp | '-' = $state(attr.typ || '-') let userEditable = $state(attr.user_editable || false); + let showMakeEditable = $state(false); $effect(() => { @@ -43,6 +47,7 @@ name = attr.name; desc = attr.desc; defaultValue = attr.default_value; + typ = attr.typ || '-'; userEditable = attr.user_editable || false; showMakeEditable = false; @@ -66,6 +71,7 @@ name, desc: desc || undefined, default_value: defaultValue || undefined, + typ: typ != '-' ? typ : undefined, user_editable: userEditable || false, } @@ -107,6 +113,14 @@ placeholder={ta.attrs.defaultValue} {width} /> +
+ {ta.attrs.typ} + +
diff --git a/frontend/src/lib/admin/clients/ClientConfig.svelte b/frontend/src/lib/admin/clients/ClientConfig.svelte index 6ce3246e8..00292e0dd 100644 --- a/frontend/src/lib/admin/clients/ClientConfig.svelte +++ b/frontend/src/lib/admin/clients/ClientConfig.svelte @@ -33,11 +33,13 @@ client = $bindable(), clients, scopesAll, + attrsEmail, onSave, }: { client: ClientResponse, clients: ClientResponse[], scopesAll: string[], + attrsEmail: string[], onSave: () => void, } = $props(); @@ -59,6 +61,9 @@ let postLogoutRedirectURIs: string[] = $state(client.post_logout_redirect_uris ? Array.from(client.post_logout_redirect_uris) : []); let backchannel_logout_uri: string = $state(client.backchannel_logout_uri || ''); let restrict_group_prefix: string = $state(client.restrict_group_prefix || ''); + let cust_email_mapping: string | undefined = $state(client.cust_email_mapping); + let cust_email_mapping_exists = $derived(cust_email_mapping !== undefined); + let scimEnabled = $state(client.scim !== undefined); let scim: ScimClientRequestResponse = $state({ @@ -117,6 +122,7 @@ origins = client.allowed_origins ? Array.from(client.allowed_origins) : []; redirectURIs = Array.from(client.redirect_uris); postLogoutRedirectURIs = client.post_logout_redirect_uris ? Array.from(client.post_logout_redirect_uris) : []; + cust_email_mapping = client.cust_email_mapping; flows.authorizationCode = client.flows_enabled.includes('authorization_code'); flows.clientCredentials = client.flows_enabled.includes('client_credentials'); @@ -204,6 +210,7 @@ contacts: contacts.length > 0 ? contacts : undefined, backchannel_logout_uri: backchannel_logout_uri || undefined, restrict_group_prefix: restrict_group_prefix || undefined, + cust_email_mapping: cust_email_mapping || undefined, } if (flows.authorizationCode) { @@ -311,6 +318,29 @@ width={inputWidth} pattern={PATTERN_GROUP} /> + {ta.clients.custEmailMappingExplanation} +
+ {ta.clients.custEmailMapping} + { + cust_email_mapping = attrsEmail.length > 0 && isChecked ? attrsEmail[0] : undefined; + }} + /> + + {#if cust_email_mapping } + + {/if} +
+ {#if attrsEmail.length === 0} + {ta.clients.custEmailMappingNoAttrs} + {/if}

Authentication Flows

diff --git a/frontend/src/lib/admin/clients/ClientDetails.svelte b/frontend/src/lib/admin/clients/ClientDetails.svelte index 848ab9ae1..e6e9cde36 100644 --- a/frontend/src/lib/admin/clients/ClientDetails.svelte +++ b/frontend/src/lib/admin/clients/ClientDetails.svelte @@ -13,11 +13,13 @@ client, clients, scopesAll, + attrsAll, onSave, }: { client: ClientResponse, clients: ClientResponse[], scopesAll: string[], + attrsAll: string[], onSave: () => void, } = $props(); @@ -50,7 +52,7 @@
{#if selected === ta.nav.config} - + {:else if selected === 'Secret'} {:else if selected === 'Branding'} diff --git a/frontend/src/lib/admin/clients/Clients.svelte b/frontend/src/lib/admin/clients/Clients.svelte index 601ffbff9..a1459c90e 100644 --- a/frontend/src/lib/admin/clients/Clients.svelte +++ b/frontend/src/lib/admin/clients/Clients.svelte @@ -13,6 +13,7 @@ import ClientAddNew from "$lib5/admin/clients/ClientAddNew.svelte"; import ClientDetails from "$lib5/admin/clients/ClientDetails.svelte"; import {useTrigger} from "$state/callback.svelte"; + import type { UserAttrConfigResponse } from "$api/types/user_attrs"; let refAddNew: undefined | HTMLButtonElement = $state(); let tr = useTrigger(); @@ -26,6 +27,7 @@ let client: undefined | ClientResponse = $state(); let cid = useParam('cid'); let scopesAll: string[] = $state([]); + let attrsEmail: string[] = $state([]); const searchOptions = ['ID']; let searchOption = $state(searchOptions[0]); @@ -35,6 +37,7 @@ onMount(() => { fetchClients(); fetchScopes(); + fetchEmailAttrs(); }); $effect(() => { @@ -68,6 +71,14 @@ } } + async function fetchEmailAttrs() { + let res = await fetchGet('/auth/v1/users/attr'); + if (res.body) { + attrsEmail = res.body.values.filter((a) => a.typ && a.typ === 'email' && !a.user_editable).map((a) => a.name); + } else { + err = res.error?.message || 'Error'; + } + } function onChangeOrder(option: string, direction: 'up' | 'down') { let up = direction === 'up'; if (option === orderOptions[0]) { @@ -133,7 +144,7 @@
{#if client} - + {/if}
diff --git a/frontend/src/lib/form/InputCheckbox.svelte b/frontend/src/lib/form/InputCheckbox.svelte index f20a17cd4..1c15eeda6 100644 --- a/frontend/src/lib/form/InputCheckbox.svelte +++ b/frontend/src/lib/form/InputCheckbox.svelte @@ -8,6 +8,7 @@ borderColor = 'hsl(var(--bg-high))', name, children, + onclickOverride, }: { checked: boolean, disabled?: boolean, @@ -15,9 +16,15 @@ borderColor?: string, name?: string, children?: Snippet, + onclickOverride?: (bool: boolean) => void, } = $props(); function onclick() { + if (onclickOverride) { + onclickOverride(!checked); + return; + } + checked = !checked; } diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index c495e1ef8..82325082e 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -42,6 +42,11 @@ export const EVENT_TYPES = [ 'Test', ] +export const ATTR_TYPES = [ + '-', + 'email', +] + // All TPL_* values match a possibly existing `