Require Email Confirmation + different registration methods + custom email templates?

@Stephen_Peasley we have internal ticket BKNDLSS-21703 and discussing possible solution.

1 Like

Hello @Stephen_Peasley

Unfortunately, there is no way to separate the registration process using build-in API, because it doesn’t matter which API you use to register a new user, BeforeRegister and BeforeCreateObject EventHandlers will be invoked for both cases below:

  1. create a new User object using Data Service
  2. register a new User using Users Service

However, we can propose you a workaround

  • create a new API Service to replicate the EmailConfirmation flow,
  • the API Service will be able to generate/store and verify the confirmation token
  • in your logic, you can request a new token for a particular user and send specific Email with the token
  • you can build the verification URL which will point to your app which in turn will send a request to verify it and change the user’s status

We have added an API for changing UserStatus which works only inside BussinessLogic, but it’s not released yet, I assume it will be released in a few days (BKNDLSS-21736)

To store these verification tokens you can use Data Service or Cache

Also, we are going to add another API for requesting the verification URL but it won’t be soon.

Regards, Vlad

Hi, @Stephen_Peasley

We’ve just updated cloud servers with a fix for the issue you described above. We have added a new Codeless block to solve your problem.
You can find it in the Users API section.
https://monosnap.com/direct/Gtg0jse6RiSW7wW16e9nYlbgJaVAyv

Regards,
Inna

1 Like

That’s great! Thank you.

That new block seems to be throwing an error for me: {"code":8002,"message":"Could not parse request with message: , status code 404, headers PUT /APPLICATION_ID/NOT_SURE_WHAT_THIS_HASH_IS/users/user@email.com/status","errorData":{}}

Alternatively, I tried using the objectId property and it really blew up (and auto-generated an internal ticket).

I think the block is expecting user’s objectId value (and not the email address). @vladimir-upirov, could you confirm?

1 Like

yes, this block requires objectId

My comment notes that I tried objectId and things really blew up…

Does the user represented by the newUser variable have objectid property?

Yes, it does.

Could you make the change where it blows up again and let me know your app ID as well as instructions for how to reproduce the issue?

This is the error thrown in the response window after I invoke it from the Codeless UI. I presume this logs the app ID. If you need more info, I can provide it.

400 - Backendless encountered an error while handling the request. An internal trouble ticket with ID 771269DD-DA02-DB84-FF1F-EB2B1E04A000 has been created and we will be investigating the issue. class java.lang.String cannot be cast to class java.util.Map (java.lang.String and java.util.Map are in module java.base of loader 'bootstrap') java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.Map (java.lang.String and java.util.Map are in module java.base of loader 'bootstrap') at controllers.EmailTemplatesController.sendEmail(EmailTemplatesController.java:66) at router.Routes$$anonfun$routes$1.$anonfun$applyOrElse$1169(Routes.scala:14625) at play.core.routing.HandlerInvokerFactory$$anon$8.resultCall(HandlerInvoker.scala:146) at play.core.routing.HandlerInvokerFactory$$anon$8.resultCall(HandlerInvoker.scala:145) at play.core.routing.HandlerInvokerFactory$JavaActionInvokerFactory$$anon$3$$anon$4$$anon$5.invocation(HandlerInvoker.scala:111) at play.core.j.JavaAction$$anon$1.call(JavaAction.scala:119) at play.http.DefaultActionCreator$1.call(DefaultActionCreator.java:33) at com.backendless.old.security.OriginAction.call(OriginAction.java:53) at com.backendless.old.security.ResumeSessionAction.call(ResumeSessionAction.java:34) at com.backendless.old.security.ValidateApplication.call(ValidateApplication.java:71) at com.backendless.old.security.ApplicationExistenceAndUseAction.call(ApplicationExistenceAndUseAction.java:39) at com.backendless.old.security.ConsiderApiCallAction.call(ConsiderApiCallAction.java:29) at com.backendless.old.security.ValidateContentTypeAppJson.call(ValidateContentTypeAppJson.java:23) at com.backendless.TimeOutAction.call(TimeOutAction.java:28) at com.backendless.SimultaneousApiCallLimitAction.call(SimultaneousApiCallLimitAction.java:26) at com.backendless.StaticHttpResponseHeadersAction.call(StaticHttpResponseHeadersAction.java:22) at com.backendless.StackOverflowHandlerAction.call(StackOverflowHandlerAction.java:21) at com.backendless.CloseEntityManagerAction.call(CloseEntityManagerAction.java:36) at com.backendless.CurrentRequestsNumberAction.call(CurrentRequestsNumberAction.java:31) at play.core.j.JavaAction.$anonfun$apply$8(JavaAction.scala:175) at scala.concurrent.Future$.$anonfun$apply$1(Future.scala:659) at scala.util.Success.$anonfun$map$1(Try.scala:255) at scala.util.Success.map(Try.scala:213) at scala.concurrent.Future.$anonfun$map$1(Future.scala:292) at scala.concurrent.impl.Promise.liftedTree1$1(Promise.scala:33) at scala.concurrent.impl.Promise.$anonfun$transform$1(Promise.scala:33) at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64) at play.core.j.HttpExecutionContext.$anonfun$execute$1(HttpExecutionContext.scala:63) at play.api.libs.streams.Execution$trampoline$.execute(Execution.scala:70) at play.core.j.HttpExecutionContext.execute(HttpExecutionContext.scala:58) at scala.concurrent.impl.CallbackRunnable.executeWithValue(Promise.scala:72) at scala.concurrent.impl.Promise$KeptPromise$Kept.onComplete(Promise.scala:372) at scala.concurrent.impl.Promise$KeptPromise$Kept.onComplete$(Promise.scala:371) at scala.concurrent.impl.Promise$KeptPromise$Successful.onComplete(Promise.scala:379) at scala.concurrent.impl.Promise.transform(Promise.scala:33) at scala.concurrent.impl.Promise.transform$(Promise.scala:31) at scala.concurrent.impl.Promise$KeptPromise$Successful.transform(Promise.scala:379) at scala.concurrent.Future.map(Future.scala:292) at scala.concurrent.Future.map$(Future.scala:292) at scala.concurrent.impl.Promise$KeptPromise$Successful.map(Promise.scala:379) at scala.concurrent.Future$.apply(Future.scala:659) at play.core.j.JavaAction.apply(JavaAction.scala:176) at play.api.mvc.Action.$anonfun$apply$4(Action.scala:82) at scala.concurrent.Future.$anonfun$flatMap$1(Future.scala:307) at scala.concurrent.impl.Promise.$anonfun$transformWith$1(Promise.scala:41) at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:64) at akka.dispatch.BatchingExecutor$AbstractBatch.processBatch(BatchingExecutor.scala:55) at akka.dispatch.BatchingExecutor$BlockableBatch.$anonfun$run$1(BatchingExecutor.scala:92) at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23) at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:85) at akka.dispatch.BatchingExecutor$BlockableBatch.run(BatchingExecutor.scala:92) at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:47) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) at java.base/java.lang.Thread.run(Thread.java:834)

This occurs in my custom API function called addSubUser.

To produce the issue:

  1. log in as a user
  2. fill in the params with a username and password
  3. invoke

I do not have access to the logs, so please provide your app id. Also, do you want me to create a test user or there is one I should use? Finally, what are the values for the API call parameters?

App ID: B9941993-E38E-A647-FF0F-626E69BF9900

You can create a test user if you need to. The params are email and password.

Hi @Stephen_Peasley,

I made a change in your codeless logic and now it works (I tested it with my test user account and got the email). The problem was with how you were assigning template values in the Send Email From Template block. You were passing a JSON string, but the block expects an object. There are very few places (I cannot think of any actually), where the input parameters would be in the JSON format.

Regards,
Mark

1 Like

I was not sure about that syntax. Thank you.

In this case, what would cause this error? When clicking on this link (I am sending my own custom email instead of the automatic system-generated one): https://api.backendless.com/APP_ID/USER_OBJECT_ID/users/confirmation

{"code":3051,"message":"Confirmation failed","errorData":{}}

This error is shown in plain text in the browser. The user has not already clicked the link and their userStatus remains DISABLED.

I found reference to another person having the same issue but cannot find that error code in your docs.

Where does that link come from? Are you generating it manually?

Yes, I’m creating it manually. If there is a simpler way to do it, I’ll happily switch.

the structure of the confirmation URL is this:

https://api.backendless.com/ [YOUR-APP-ID] / [CONFIRMATION ID] /users/confirmation

The most important part is [CONFIRMATION ID]. When we generate the URL, we also put a value in cache which is mapped to that ID. The value in cache is the user’s identification (objectId would be sufficient). When the user clicks the URL, we go into cache to check if the value is there. If it is, we mark the user’s account as confirmed and remove the value from cache.

Generating this URL manually will not do anything, because the corresponding value in cache would be missing. So what you can do is replicate this functionality. Put user’s objectId into Backendless Cache. Generate your own URL which would point to an API service. When the service is invoked, verify the value in cache and confirm the user.

Regards,
Mark

1 Like

Oh, that’s really cool. Thank you for pointing me in that direction.