Salesforce Marketing Cloud code resource can't process an XHR request from an amp-form component - xmlhttprequest

I am using an amp-form inside a dynamic mail and want it to send some data to a code resource sitting on a Salesforce Marketing Cloud org. The request seems to be fine, but it never reaches the code resource. There is always the same error saying that the "Access-Control-Allow-Origin" header is missing. The content of the code resource is taken straight from the AMP documentation on CORS.
Code Resource
<script runat="server" executioncontexttype="post" executioncontextname=corsinampforemail>
Platform.Load("core", "1");
if (Platform.Request.GetRequestHeader("AMP-Email-Sender")) {
var senderEmail = Platform.Request.GetRequestHeader("AMP-Email-Sender")
if (isValidSender(senderEmail)) {
HTTPHeader.SetValue("AMP-Email-Allow-Sender", senderEmail)
} else {
Platform.Function.RaiseError("Sender Not Allowed",true,"statusCode","3");
}
} else if (Platform.Request.GetRequestHeader("Origin")) {
var requestOrigin = Platform.Request.GetRequestHeader("Origin")
if (Platform.Request.GetQueryStringParameter("__amp_source_origin")) {
var senderEmail = Platform.Request.GetQueryStringParameter("__amp_source_origin");
if (isValidSender(senderEmail)) {
HTTPHeader.SetValue("Access-Control-Allow-Origin", requestOrigin);
HTTPHeader.SetValue("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin");
HTTPHeader.SetValue("AMP-Access-Control-Allow-Source-Origin", senderEmail);
} else {
Platform.Function.RaiseError("Invalid Source Origin",true,"statusCode","3");
}
} else {
Platform.Function.RaiseError("Source Origin Not Present",true,"statusCode","3");
}
} else {
Platform.Function.RaiseError("Origin and Sender Not Present",true,"statusCode","3");
}
</script>
amp-form
<form id="test-form" method="post"
action-xhr="https://.../..."
enctype="multipart/form-data">
<input type="submit" value="Submit">
<div submit-success>
<span>Submit successful</span>
</div>
<div submitting>
<span>Submitting...</span>
</div>
<div submit-error>
<span>Error</span>
</div>
</form>
Does anyone know what the problem is here?

Related

How to show page before executing OnGet?

I have a razor page that shows a 'Please wait' box and the OnGet method does some stuff that might take a few seconds and ends with a LocalRedirect.
The razor code:
#page
#inject IStringLocalizer<Startup> localizer
<div class="login-page">
<div class="login-box">
<div class="card">
<div class="card-body login-card-body">
<div class="help-block text-center">
<div class="spinner-border" role="status" />
</div>
<p class="login-box-msg">#localizer["PleaseWait"]</p>
</div>
</div>
</div>
</div>
And the code-behind:
public async Task<IActionResult> OnGet()
{
//Do some stuff that takes a few seconds...
return LocalRedirect("/Dashboard");
}
Everything is working apart from the page first being shown and then executing the code.
Is it possible to first render the page (so that the users can see that something is happening) and then execute the code in the OnGet?
Move your long async routines from OnGet to a number of named handler methods. Allow the page to render a "please wait" message and use client-side code (jQuery AJAX or plain Fetch) to call the named handlers. Keep track of when they complete and when all have completed, redirect to the other page.
Here's an example where the OnGet simply renders the page (can include a "please wait" message"), and a number of named handlers simulate routines of varying length:
public void OnGet()
{
}
public async Task OnGetTwoSeconds()
{
await Task.Delay(2000);
}
public async Task OnGetThreeSeconds()
{
await Task.Delay(3000);
}
public async Task OnGetFiveSeconds()
{
await Task.Delay(5000);
}
The following script goes in the Razor page itself. It consists of three variables for tracking task completion and a method that redirects to the home page when all three have completed. Each of the named handlers is called by the code and sets its tracking variable to true on completion as well as calling the redirect function:
#section scripts{
<script>
let twosecondsdon = false;
let threesecondsdone = false;
let fivesecondsdone = false;
function redirect(){
if (twosecondsdone && threesecondsdone && fivesecondsdone) {
location.href = '/';
}
}
fetch('?handler=TwoSeconds').then(() => {
twosecondsdone = true;
redirect();
})
fetch('?handler=ThreeSeconds').then(() => {
threesecondsdone = true;
redirect();
})
fetch('?handler=FiveSeconds').then(()=>{
fivesecondsdone = true;
redirect();
})
</script>
}
When all three complete, the redirect function does its thing.
Is it possible to first render the page (so that the users can see
that something is happening) and then execute the code in the OnGet?
Yes you can do that. Currently, I am showing you the delay counter where you can replace your page. Please follow the steps below:
HTML:
Script:
#section scripts {
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"></script>
<script>
$(document).ready(function () {
var counter = 5;
(function countDown() {
if (counter-- > 0) {
$('#timer').text("Please wait... we are redirecting you to register page..." + counter + ' s');
setTimeout(countDown, 1000);
} else {
window.location.href = "https://localhost:44361/userlog/ViewCalculateAge";// Here put your controller URL where you would like to redirect
}
})();
});
</script>
}
Output:

How to authorize POST form upload in Ktor?

I want to create a simple form where user enters some string (key that authorizes them to upload a file) and the file they want to upload (no size limit, can be even 10GB or more).
The problem I have is that I don't know how to verify the code BEFORE accepting the file.
So far I have this code that disallows any upload even with a valid code since the uploaded file seems to be always the first form element to be checked.
(when I reversed the order of elements in the form this code didn't handle the request at all)
var isAuth = false
multipart.forEachPart { part ->
when (part) {
is PartData.FormItem -> {
val name = part.name
if(name != null && name == "key")
isAuth = isKeyValid(part.value)
}
is PartData.FileItem -> {
if(!isAuth) {
call.respond("Request not authorized")
call.response.status(HttpStatusCode.Forbidden)
part.dispose
return#forEachPart
}
if(part.originalFileName.isNullOrEmpty() || part.originalFileName!!.isBlank()) {
call.respond("Illegal filename")
call.response.status(HttpStatusCode.BadRequest)
return#forEachPart
}
val targetDir = File(uploadDir.path + File.separator + randomId)
targetDir.mkdir()
val targetFile = File(targetDir.path + File.separator + part.originalFileName)
targetFile.createNewFile()
sb.append(randomId)
sb.append("/")
sb.append(part.originalFileName)
sb.append("\n")
part.streamProvider().use { input -> targetFile.outputStream().buffered().use { output -> input.copyToSuspend(output) } }
}
}
part.dispose
}
HTML form I'm using:
<html>
<body>
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file">
<input type="text" name="key">
<input type="submit" value="upload" name="submit">
</form>
</body>
</html>
Add Authentication feature
implementation "io.ktor:ktor-auth:$ktor_version"
Install the feature
install(Authentication) { //set type of authenction here }
Wrap your call in authenticate {} block
authenticate("auth") {
post(FORM) {
}
}
More Info: Ktor Authentication

Display Session timeout warning message before Session expires in ASP.NET Core

I can able to set the session end the below code.
services.AddSession(options => {
options.IdleTimeout = TimeSpan.FromMinutes(2);
});
I need to extend the session after 20 minutes and if show the session time out warning message to the user and so the user can extend their time out from the application UI.
You already have session timeout code in your question. By the way, default value is 20 minutes. If you want more information, you can read more at Configuring Session.
As far as, I know ASP.NET doesn't have a build-in mechanism to display session expire notification message. So, we have to write our own.
Here is mine, and here is the usage. You are feel free to use it. Since I use Kendo UI, I use Kendo UI Window for the dialog. You could replace it with jQuery UI, if you do not want to use Kendo UI.
_SessionExpireNotification.cshtml
I keep the setting inside appsettings.json. You could hard coded them in this file.
#using Asp.Core
#using Microsoft.Extensions.Options
#using Asp.Web.Common
#inject IUserSession UserSession
#inject IOptions<AppSettings> AppSettings
#if (UserSession.IsAuthenticated)
{
#(Html.Kendo().Window()
.Name("SessionExpireNotification")
.Title("Need More Time?")
.Modal(true)
.Content(#<text>
<p>
Your session is about to expire. You will be automatically signed out in
</p>
<h2 style="margin-top: 0">
<span id="logout-counter-span">0#(AppSettings.Value.CookieAuthentication.SessionExpireNotificationMinutes):00</span>
</h2>
<p>
To continue your session, select <strong>Stay Signed In</strong>.
</p>
<p>
<button id="stay-logged-in-button" type="button" class="btn btn-primary">
Stay Signed In
</button>
<button id="signout-button" type="button" class="btn btn-default">
Sign out
</button>
</p>
</text>)
.Width(450)
.Visible(false)
.Events(ev => ev.Close("onSessionExpireNotificationClose"))
)
<script>
var notificationInterval,
logoutInterval,
logoutCounterSpan;
function startNotificationCounter() {
var counter = #AppSettings.Value.CookieAuthentication.ExpireMinutes;
notificationInterval = setInterval(function() {
counter--;
if (counter === #AppSettings.Value.CookieAuthentication.SessionExpireNotificationMinutes) {
$("#SessionExpireNotification").data("kendoWindow").center().open();
startLogoutCounter();
}
},
60000);
}
function startLogoutCounter() {
var counter = #(AppSettings.Value.CookieAuthentication.SessionExpireNotificationMinutes*60);
logoutInterval = setInterval(function() {
counter--;
if (counter < 0) {
$("#logoutForm").submit();
} else {
var m = Math.floor(counter / 60);
var s = Math.floor(counter % 60);
var mDisplay = m < 10 ? "0" + m : m;
var sDisplay = s < 10 ? "0" + s : s;
logoutCounterSpan.text(mDisplay + ":" + sDisplay);
}
},
1000);
}
function resetCounters() {
clearInterval(notificationInterval);
clearInterval(logoutInterval);
logoutCounterSpan.text("0#(AppSettings.Value.CookieAuthentication.SessionExpireNotificationMinutes):00");
startNotificationCounter();
}
function onSessionExpireNotificationClose() {
resetCounters();
}
$(function() {
logoutCounterSpan = $("#logout-counter-span");
startNotificationCounter();
$("#stay-logged-in-button").click(function() {
$.get("#Url.Action("Index", "KeepAlive", new {area = ""})",
null,
function(data) {
resetCounters();
$("#SessionExpireNotification").data("kendoWindow").center().close();
}
);
});
$("#signout-button").click(function() {
$("#logoutForm").submit();
});
});
</script>
}
Extending the session timeout is easy. You just call a dummy action method.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Asp.Web.Controllers
{
[AllowAnonymous]
public class KeepAliveController : Controller
{
//
// GET: /KeepAlive
[AllowAnonymous]
public ActionResult Index()
{
return Content("I am alive!");
}
}
}

Aurelia Validation validation error detected, but no error message

I have a super simple code I'm trying to validate:
<template>
<form role="form" submit.delegate="submit()" validate.bind="validation">
<div class="form-group">
<label>Test Field</label>
<input type="text" value.bind="testField" class="form-control" validate="Description" placeholder="What needs to be done?" />
<button type="submit">Submit</button>
</div>
</form>
</template>
With the following viewmodel
define(["require", "exports", "../scripts/HttpClient", "aurelia-validation", "aurelia-framework"], function(require, exports, HttpClient) {
var AureliaValidation = require('aurelia-validation').Validation;
var MyViewModel = (function () {
function MyViewModel(httpClient, aureliaValidation, isReadyCallback) {
this.httpClient = httpClient;
var self = this;
self.setupValidation(aureliaValidation);
}
MyViewModel.prototype.activate = function (params, queryString, routeConfig) {
};
MyViewModel.prototype.setupValidation = function (validation) {
this.testField = "";
this.validation = validation.on(this).ensure('testField');
//validation
// .on(this.serviceMetadata.ServiceData[0])
// .ensure('Value');
this.validation = this.validation.notEmpty().maxLength(3);
};
MyViewModel.prototype.submit = function () {
debugger;
if (this.validation.checkAll()) {
//Do Something
}
return null;
};
MyViewModel.inject = [HttpClient, AureliaValidation];
return MyViewModel;
})();
return MyViewModel;
});
Now I got it working for the most part, and the validation is showing false on submit check, the textbox outline color changes etc., however it's not injecting the validation error messages into the DOM. There's no script error message either, how can I troubleshoot this?
Yes, I can see the validation messages in the validationProperties, but they're not written to the UI.
If your browser allows it, find the JSPM packages in the sources and put a breakpoint here, it's the point where the view strategy looks for labels to append error messages to. If you'd have this code in the open, I'd be happy to have a look for you.
Also, what version of aurelia/aurelia-validation are you using?
And finally, did you modify your sample before posting?
`<input value.bind="testField" validate="Description" />`
These two attributes are contradictory. It binds the value to testField, but then you use the validate attribute to explicitly show validation messages for property "Description".

How do people post on my guestbook without entering the captcha?

I added a textbox to make sure spammers don't post on my guestbook. I wrote the question in Chinese so it's harder (I hope), but it doesn't seem to work. As you can see, the spammers still can post. How is it done?
http://www.badmintontw.com/guestbook.php
Thank you.
The form code:
<script>
$(document).ready(function(){
$('#guestbook').submit( function(){
return valid_form();
});
});
function valid_form()
{
if ( $('#content').val() == ''){
alert('沒有留言');
return false;
}
if ($('#sum').val() != 12){
alert('請輸入正確的數字');
return false;
}
return true;
}
</script>
<h1>留言板</h1>
<p>對本網站有任何想法、問題,歡迎在此留言!</p>
<form name="guestbook" id="guestbook" action="guestbook_process.php" method="post">
<!--<input type = "hidden" name = "post_id" value = "<?php echo $id; ?>">-->
<textarea id="content" name = "content" placeholder="留言"></textarea>
<br />五加七等於多少? <input type="text" name="sum" id="sum">
<br /><input type="submit" name="submit" id="submit" value="送出">
</form>
And guestbook_process.php contains:
if(isset($_POST['submit'])){
if($_POST['content'] != ""){
$guestbook_insert_sql = "insert into badminton.guestbook(guestbook_ip, content, time) values (:guestbook_ip, :content, current_timestamp)";
$result = $db->prepare($guestbook_insert_sql);
$result->execute(array( ':guestbook_ip' => $_SERVER['REMOTE_ADDR'],
':content' => $_POST['content']));
header("Location: guestbook.php");
}else{
echo "沒有內容";
}
}
You need to check the answer to the captcha on the server side (e.g. in your guestbook_process.php). The spammer just doesn't execute your Javascript. Just try to disable javascript in your browser and see for yourself.
what is 5 + 7 is your question..
what you can do is:
<?php
$int = 12;
if(!filter_var($12, FILTER_VALIDATE_INT))
{
//your code
}
else
{
echo("Please enter a correct value");
}
?>
I don't know if it's the best solution but this might work..