Comments (11)
The issue appears to be linked to internalization. After turning it on, I was able to reproduce the problem.
I'll dig deeper into this when I have some free time and your input could be valuable in finding a solution.
Since usually we invite a non-existent user, we can't determine their locale, therefore, one option could be using the default realm locale for invitations. Alternatively, we might think about using the sender's locale and/or improving the API to include locale as an option. What do you think about these solutions for your situation?
from keycloak-multi-tenancy.
In the new version with the latest changes, internationalization should work. I also added the ability to specify the locale of invitations.
@oleaasbo please check and if everything works for you, I will close the issue.
from keycloak-multi-tenancy.
Thank you @anarsultanov!
Can confirm that it works! My test only included turning internalization on again.
Keycloak-multi-tenancy v22.1.0
Keycloak v22.0.3
from keycloak-multi-tenancy.
Hi @oleaasbo,
Thank you for your feedback!
To customize the link in the invitation email, you have a few options here:
- You can follow the standard Keycloak email customization process by creating a custom theme . Within this theme, you can either customize the email template (
invitation-email.ftl
) or modify the email messages (invitationEmailBody
andinvitationEmailBodyHtml
). This allows you to tailor the email content, including the link, to your specific needs. - Alternatively, you can make changes in the extension source code by updating the same files, and then rebuild it.
Regarding your feature request, it's an interesting idea. However, please keep in mind that the extension doesn't have direct control over the application's behavior. Asking every user to provide their application URL for email customization and implementing a redirect back to Keycloak might not be the most user-friendly approach.
A potentially more elegant solution could be to introduce an endpoint within the extension to generate action tokens that allow users to join a tenant. Then, it would be up to individual applications to request and provide these tokens to invited users. While I can't promise immediate implementation, you're welcome to submit a pull request if you'd like to contribute to this feature's development.
If you have any more questions or suggestions, please don't hesitate to reach out.
from keycloak-multi-tenancy.
I like option 1, however I am not able to get it to work. I get an error when email should be sent.
2023-09-13 23:01:47,091 ERROR [org.keycloak.services] (executor-thread-3) KC-SERVICES0029: Failed to send email: org.keycloak.email.EmailException: Failed to template email
2023-09-14T01:01:47.094530966+02:00 at org.keycloak.email.freemarker.FreeMarkerEmailTemplateProvider.processTemplate(FreeMarkerEmailTemplateProvider.java:242)
2023-09-14T01:01:47.094542358+02:00 at org.keycloak.email.freemarker.FreeMarkerEmailTemplateProvider.send(FreeMarkerEmailTemplateProvider.java:257)
at org.keycloak.email.freemarker.FreeMarkerEmailTemplateProvider.send(FreeMarkerEmailTemplateProvider.java:252)
2023-09-14T01:01:47.094557368+02:00 at dev.sultanov.keycloak.multitenancy.util.EmailUtil.sendEmail(EmailUtil.java:50)
2023-09-14T01:01:47.094564008+02:00 at dev.sultanov.keycloak.multitenancy.util.EmailUtil.sendInvitationEmail(EmailUtil.java:27)
at dev.sultanov.keycloak.multitenancy.resource.TenantInvitationsResource.createInvitation(TenantInvitationsResource.java:73)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
2023-09-14T01:01:47.094595511+02:00 at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
2023-09-14T01:01:47.094609565+02:00 at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:154)
2023-09-14T01:01:47.094615843+02:00 at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:118)
2023-09-14T01:01:47.094622140+02:00 at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:560)
2023-09-14T01:01:47.094628410+02:00 at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:452)
2023-09-14T01:01:47.094634778+02:00 at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:413)
2023-09-14T01:01:47.094641462+02:00 at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:415)
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:378)
2023-09-14T01:01:47.094674122+02:00 at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:174)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:142)
2023-09-14T01:01:47.094686789+02:00 at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:168)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:142)
2023-09-14T01:01:47.094699397+02:00 at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:168)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:131)
2023-09-14T01:01:47.094712221+02:00 at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:33)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:429)
2023-09-14T01:01:47.094745726+02:00 at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:240)
2023-09-14T01:01:47.094751991+02:00 at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:154)
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:321)
at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:157)
2023-09-14T01:01:47.094771284+02:00 at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:229)
2023-09-14T01:01:47.094777737+02:00 at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:82)
2023-09-14T01:01:47.094783849+02:00 at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:147)
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:84)
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:44)
2023-09-14T01:01:47.094845151+02:00 at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
2023-09-14T01:01:47.094856938+02:00 at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:177)
2023-09-14T01:01:47.094876798+02:00 at io.vertx.ext.web.impl.RoutingContextWrapper.next(RoutingContextWrapper.java:200)
at io.quarkus.vertx.http.runtime.options.HttpServerCommonHandlers$1.handle(HttpServerCommonHandlers.java:58)
at io.quarkus.vertx.http.runtime.options.HttpServerCommonHandlers$1.handle(HttpServerCommonHandlers.java:36)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:177)
2023-09-14T01:01:47.094938496+02:00 at io.vertx.ext.web.impl.RoutingContextWrapper.next(RoutingContextWrapper.java:200)
2023-09-14T01:01:47.094947009+02:00 at org.keycloak.quarkus.runtime.integration.web.QuarkusRequestFilter.lambda$createBlockingHandler$0(QuarkusRequestFilter.java:82)
2023-09-14T01:01:47.094954007+02:00 at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:576)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.UnsupportedOperationException
at dev.sultanov.keycloak.multitenancy.util.EmailUtil$Recipient.getFirstAttribute(EmailUtil.java:128)
2023-09-14T01:01:47.095026494+02:00 at org.keycloak.locale.DefaultLocaleSelectorProvider.getUserProfileSelection(DefaultLocaleSelectorProvider.java:108)
at org.keycloak.locale.DefaultLocaleSelectorProvider.getUserLocale(DefaultLocaleSelectorProvider.java:71)
2023-09-14T01:01:47.095050287+02:00 at org.keycloak.locale.DefaultLocaleSelectorProvider.resolveLocale(DefaultLocaleSelectorProvider.java:50)
at org.keycloak.services.DefaultKeycloakContext.resolveLocale(DefaultKeycloakContext.java:133)
at org.keycloak.email.freemarker.FreeMarkerEmailTemplateProvider.processTemplate(FreeMarkerEmailTemplateProvider.java:212)
2023-09-14T01:01:47.095070825+02:00 ... 50 more
2023-09-14T01:01:47.095077050+02:00
I have created a custom theme and selected it under realm settings -> themes -> Email themes
/opt/keycloak/themes/tenant-invites/email/theme.properties
parent=base
import=common/keycloak
/opt/keycloak/themes/tenant-invites/email/messages/messages_en.properties
reviewInvitationsHeader=Review invitations
reviewInvitationsInfo=You have been invited to join the following tenants. Uncheck the box to decline the invitation.
selectTenantHeader=Select tenant
selectTenantInfo=Select the tenant you would like to log in to.
createTenantHeader=Create tenant
createTenantInfo=You need to create a tenant.
tenantName=Tenant name
tenantEmptyError=Please specify value.
tenantExistsError=Tenant already exists.
invitationEmailSubject=Invitation to {0}
invitationEmailBody=You have been invited to join {0}. To accept or decline this invitation, log in to your account or register using the link below.\n\n{1}
invitationEmailBodyHtml=<p>You have been invited to join {0}. To accept or decline this invitation, log in to your account or register using the link below.</p><p><a href="{1}">Link to account page</a></p>
invitationAcceptedEmailSubject=Your invitation has been accepted
invitationAcceptedEmailBody=Your invitation for {0} to join {1} has been accepted.
invitationAcceptedEmailBodyHtml=<p>Your invitation for {0} to join {1} has been accepted.</p>
invitationDeclinedEmailSubject=Your invitation has been declined
invitationDeclinedEmailBody=Your invitation for {0} to join {1} has been declined.
invitationDeclinedEmailBodyHtml=<p>Your invitation for {0} to join {1} has been declined.</p>
The messages are identical to the original (copied from keycloak-multi-tenancy/resources/theme-resources/messages/messages_en.properties)
What do i do wrong?
Thanks for the help!
from keycloak-multi-tenancy.
I'm sorry, but I'm not entirely sure what might be causing the issue in your specific case.
I attempted to create a Docker image with the extension and custom theme, and after configuring it in Realm, it was able to successfully use my custom email messages. Below is the Dockerfile I used for this, assuming the extension is located in the providers
directory and the custom theme is in themes
:
FROM quay.io/keycloak/keycloak:22.0.1 as builder
COPY /providers/ /opt/keycloak/providers/
RUN /opt/keycloak/bin/kc.sh build
FROM quay.io/keycloak/keycloak:22.0.1
COPY --from=builder /opt/keycloak/ /opt/keycloak/
COPY /themes/ /opt/keycloak/themes/
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
It's possible that it worked for me because I separated the build step. You might want to consider doing the same and building an optimized image if you haven't already.
from keycloak-multi-tenancy.
I did not separate the build stage like you did here but tried it with no different outcome.
FROM quay.io/keycloak/keycloak:latest as builder
ENV KC_CACHE=ispn
ENV KC_CACHE_STACK=kubernetes
ENV KC_TRANSACTION_XA_ENABLED=true
# Enable health and metrics support
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true
ENV KC_HTTP_RELATIVE_PATH=/auth/
ENV KC_FEATURES=docker,account-api,admin-api,authorization,client-policies
# Configure a database vendor
ENV KC_DB=postgres
# Organization plugin
COPY ./keycloak-multi-tenancy/target/keycloak-multi-tenancy-22.0.0.jar /opt/keycloak/providers
# Apple IdP plugin
ADD --chown=keycloak:keycloak https://github.com/klausbetz/apple-identity-provider-keycloak/releases/download/1.7.0/apple-identity-provider-1.7.0.jar /opt/keycloak/providers/apple-identity-provider-1.7.0.jar
RUN /opt/keycloak/bin/kc.sh build
FROM quay.io/keycloak/keycloak:latest
COPY --from=builder /opt/keycloak/ /opt/keycloak/
# Copy all themes
COPY --chown=keycloak:root ./themes /opt/keycloak/themes
ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "--spi-theme-static-max-age=-1", "--spi-theme-cache-themes=false", "--spi-theme-cache-templates=false"]
No matter what email theme i select i get the errors from earlier. Does not seem like i am able to deselect themes now after selecting one.
When you tested with a custom theme did you use the two files as described in my previous post?
from keycloak-multi-tenancy.
Yes, I added exactly two files as you described.
I'm guessing something else in your configuration is causing this issue. Could you try enabling debug logging and see if you can get more information?
from keycloak-multi-tenancy.
The logging did not provide additional information about this error. However the exception is thrown from:
keycloak-multi-tenancy/src/main/java/dev/sultanov/keycloak/multitenacy/util/EmailUtil.java
Caused by: java.lang.UnsupportedOperationException
at dev.sultanov.keycloak.multitenancy.util.EmailUtil$Recipient.getFirstAttribute(EmailUtil.java:128)
@Override
public String getFirstAttribute(String name) {
throw new UnsupportedOperationException();
}
This is included in the error log from previous comment and did also appear now with KC_LOG_LEVEL="fatal,error,warn,info,debug"
from keycloak-multi-tenancy.
After disabling Internationalization we are back in business! Thank you so much for your help! Much appreciated.
I would love to see a combination of using the default realm locale if no locale is provided during the creation of the invite (Improving the API)
Going back to the feature request regarding action token invite link, I prefer you "elegant solution" of extending the API to allow me to send emails from my application. In this case the locale no longer relies on Keycloak settings and invites can easily be sent over other transports like SMS and social media, etc.
Should i open a new discussion about this feature or do you consider it relevant within this topic/discussion?
from keycloak-multi-tenancy.
I am glad to help!
I would prefer that you create a separate feature request for the invitation token as I want to improve the handling of internationalization within this issue and close it.
from keycloak-multi-tenancy.
Related Issues (18)
- Multi-Tenancy Unknown error on enabling Tenant creation in Authentication HOT 10
- Unique password policy for each tenant HOT 5
- Help Needed : Linking Tenant to IDP HOT 5
- Enhancement Request: Use of Action Token for Invitation Evaluation Triggers HOT 12
- Enhancement Request: Implement Role-Based Access Control for Tenant Creation Endpoint HOT 4
- REALM_ID, USER_ID, TENANT_ID with wrong varchar length HOT 1
- Unable to remove membership HOT 3
- Dependency Dashboard HOT 1
- Error when calling membership endpoint with search query HOT 3
- API returns 401 HOT 1
- Rename a tenant HOT 1
- Feature Request: Customizable Terminology for Multi-Tenancy in Keycloak HOT 1
- OpenAPI/Swagger documentation HOT 1
- Authenticator flow setup with IdpTenantMembershipsCreatingAuthenticator HOT 6
- Problem trying to remove tenant HOT 1
- Feature request - Include resource ID in all API POST endpoints HOT 3
- CORS issue when calling endpoints directly from my app HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from keycloak-multi-tenancy.