Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OTP Email Theming is Broken #30

Open
dcarlet opened this issue Dec 21, 2023 · 2 comments
Open

OTP Email Theming is Broken #30

dcarlet opened this issue Dec 21, 2023 · 2 comments

Comments

@dcarlet
Copy link

dcarlet commented Dec 21, 2023

So, we recently were asked to add Email One Time Passcodes to our Keycloak. I was very happy to find this repository!

However, after several hours of testing/compiling/deploying/pulling hairs out, I've found that there seems to be an issue with how the plugin itself handles theme lookup.

We deploy Keycloak into Kubernetes using the bitnami container/helm chart. We're running 22.0.5. In order to add this awesome plugin, I:

  • Clone this repository and built from master after updating the pom.xml to specify a new custom version (v0.4-KC22.0.5-custom) and updating the keycloak.version to 22.0.5 using openJDK 17 on RHEL 8.

  • a dockerfile to build a custom container:

FROM docker.io/bitnami/keycloak:22.0.5-debian-11-r4
# Copy over the plugin itself
COPY keycloak-2fa-email-authenticator-v0.4-KC22.0.5-custom.jar /opt/bitnami/keycloak/providers/
# # Copy over the template resources
COPY themes/ /opt/bitnami/keycloak/themes/
# Build it.
RUN /opt/bitnami/keycloak/bin/kc.sh build

ENTRYPOINT ["/opt/bitnami/scripts/keycloak/entrypoint.sh"]

CMD ["/opt/bitnami/scripts/keycloak/run.sh"]

I tried having the themes/ dir containing several things, and here are the results:

  1. Nothing
  2. The email code theme items per the README under /opt/bitnami/keycloak/themes/base/(etc)
  3. The email code theme as a folder under themes/ Did not work
  4. I additionally copied the themes/ out from the org.keycloak.keycloak-themes-22.0.5.jar jar so that /opt/bitnami/keycloak/themes/ looks like:
  • base
  • email-code-theme
  • keycloak
  • keycloak.v2

However, testing revealed a consistent behavior:

  • Email verification works
  • Email OTP does not work with the following stack trace:
2023-12-21 18:36:42,047 ERROR [com.mesutpiskin.keycloak.auth.email.EmailAuthenticatorForm] (executor-thread-2) Failed to send access code email. realm=d2dfd77d-5b84-484f-a89b-b7896349df06 [email protected]: org.keycloak.email.EmailException: Failed to template email
	at org.keycloak.email.freemarker.FreeMarkerEmailTemplateProvider.processTemplate(FreeMarkerEmailTemplateProvider.java:242)
	at org.keycloak.email.freemarker.FreeMarkerEmailTemplateProvider.send(FreeMarkerEmailTemplateProvider.java:257)
	at org.keycloak.email.freemarker.FreeMarkerEmailTemplateProvider.send(FreeMarkerEmailTemplateProvider.java:252)
	at com.mesutpiskin.keycloak.auth.email.EmailAuthenticatorForm.sendEmailWithCode(EmailAuthenticatorForm.java:179)
	at com.mesutpiskin.keycloak.auth.email.EmailAuthenticatorForm.generateAndSendEmailCode(EmailAuthenticatorForm.java:77)
	at com.mesutpiskin.keycloak.auth.email.EmailAuthenticatorForm.challenge(EmailAuthenticatorForm.java:44)
	at org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator.challenge(AbstractUsernameFormAuthenticator.java:66)
	at com.mesutpiskin.keycloak.auth.email.EmailAuthenticatorForm.authenticate(EmailAuthenticatorForm.java:39)
	at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:445)
	at org.keycloak.authentication.DefaultAuthenticationFlow.processFlow(DefaultAuthenticationFlow.java:249)
	at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:380)
	at org.keycloak.authentication.DefaultAuthenticationFlow.processFlow(DefaultAuthenticationFlow.java:249)
	at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:380)
	at org.keycloak.authentication.DefaultAuthenticationFlow.processFlow(DefaultAuthenticationFlow.java:271)
	at org.keycloak.authentication.AuthenticationProcessor.authenticateOnly(AuthenticationProcessor.java:1026)
	at org.keycloak.services.resources.LoginActionsService$2.authenticateOnly(LoginActionsService.java:874)
	at org.keycloak.authentication.AuthenticationProcessor.authenticate(AuthenticationProcessor.java:888)
	at org.keycloak.services.resources.LoginActionsService.processFlow(LoginActionsService.java:380)
	at org.keycloak.services.resources.LoginActionsService.brokerLoginFlow(LoginActionsService.java:904)
	at org.keycloak.services.resources.LoginActionsService.postBrokerLoginGet(LoginActionsService.java:809)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:154)
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:118)
	at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:560)
	at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:452)
	at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:413)
	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)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:174)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:131)
	at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:33)
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:429)
	at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:240)
	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)
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:229)
	at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:82)
	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)
	at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1284)
	at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:177)
	at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
	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)
	at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
	at org.keycloak.quarkus.runtime.integration.web.QuarkusRequestFilter.lambda$createBlockingHandler$0(QuarkusRequestFilter.java:82)
	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:840)
Caused by: org.keycloak.email.EmailException: Failed to template plain text email.
	at org.keycloak.email.freemarker.FreeMarkerEmailTemplateProvider.processTemplate(FreeMarkerEmailTemplateProvider.java:230)
	... 60 more
Caused by: org.keycloak.theme.FreeMarkerException: Failed to process template text/code-email.ftl
	at org.keycloak.theme.freemarker.DefaultFreeMarkerProvider.processTemplate(DefaultFreeMarkerProvider.java:52)
	at org.keycloak.email.freemarker.FreeMarkerEmailTemplateProvider.processTemplate(FreeMarkerEmailTemplateProvider.java:228)
	... 60 more
Caused by: freemarker.template.TemplateNotFoundException: Template not found for name "text/code-email.ftl".
The name was interpreted by this TemplateLoader: org.keycloak.theme.freemarker.DefaultFreeMarkerProvider$ThemeTemplateLoader@6e291479.
	at freemarker.template.Configuration.getTemplate(Configuration.java:2957)
	at freemarker.template.Configuration.getTemplate(Configuration.java:2777)
	at org.keycloak.theme.freemarker.DefaultFreeMarkerProvider.getTemplate(DefaultFreeMarkerProvider.java:66)
	at org.keycloak.theme.freemarker.DefaultFreeMarkerProvider.processTemplate(DefaultFreeMarkerProvider.java:45)
	... 61 more

Part of what I discovered is that the plugin seems to be expecting to use the template based on what the Realm Settings -> Themes -> Email Code Theme setting is, set to. However, Keycloak uses this same setting for email verification. So if we change that setting to the email-code-theme, then Email OTP works, but keycloak's email verification does not. If we set it to Base or Keycloak, then Email verification works, but Email OTP does not.

I tried looking at the java source to figure out how to figure out how to set the plugin to use a different theme but I couldn't figure it out (I haven't been a Java dev in like, 10 years, so forgive me XD). It seems to be set on this line but...clearly something isn't correct.

@kominoshja
Copy link
Contributor

This gets fixed by #21. A rebase of that branch is needed, but from my tests it still works fine.

This was referenced Oct 15, 2024
@kn3609571
Copy link
Contributor

This issue may be solved easier by including the templates as theme-resources in the JAR.
https://www.keycloak.org/docs/latest/server_development/#_theme_resource

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants