Skip to content

Commit

Permalink
Issue # 78 | Feature: Add Email Contact Form Function for Java
Browse files Browse the repository at this point in the history
  • Loading branch information
PalaniappanC committed Oct 14, 2023
1 parent c9dc35a commit b143f3e
Show file tree
Hide file tree
Showing 7 changed files with 469 additions and 0 deletions.
29 changes: 29 additions & 0 deletions java/email-contact-form/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# Ignore Gradle project-specific cache directory
.gradle

# Ignore Gradle build output directory
build
100 changes: 100 additions & 0 deletions java/email-contact-form/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# 📬 JAVA Email Contact Form Function

Sends an email with the contents of a HTML form.

## 🧰 Usage

### GET /

HTML forms for interacting with the function.

### POST /

Submit form data to send an email

**Parameters**

| Name | Description | Location | Type | Sample Value |
| ------ | --------------------------------- | ---------- | ------ | -------------------------------- |
| \_next | URL for redirect after submission | Form Param | String | `https://mywebapp.org/success` |
| \* | Any form values to send in email | Form Param | String | `Hey, I'd like to get in touch!` |

**Response**

Sample `200` Response:

```text
Location: https://mywebapp.org/success
```

Sample `400` Response:

```text
Location: https://mywebapp.org/referer?error=Error+Description
```

## ⚙️ Configuration

| Setting | Value |
| ----------------- | --------------- |
| Runtime | Java (17) |
| Entrypoint | `src/Main.java` |
| Permissions | `any` |
| Timeout (Seconds) | 15 |

## 🔒 Environment Variables

### SMTP_HOST

The address of your SMTP server. Many STMP providers will provide this information in their documentation. Some popular providers include: Mailgun, SendGrid, and Gmail.

| Question | Answer |
| ------------ | ------------------ |
| Required | Yes |
| Sample Value | `smtp.mailgun.org` |

### SMTP_PORT

The port of your STMP server. Commnly used ports include `25`, `465`, and `587`.

| Question | Answer |
| ------------ | ------ |
| Required | Yes |
| Sample Value | `25` |

### SMTP_USERNAME

The username for your SMTP server. This is commonly your email address.

| Question | Answer |
| ------------ | ----------------------- |
| Required | Yes |
| Sample Value | `no-reply@mywebapp.org` |

### SMTP_PASSWORD

The password for your SMTP server.

| Question | Answer |
| ------------ | --------------------- |
| Required | Yes |
| Sample Value | `5up3r5tr0ngP4ssw0rd` |

### SUBMIT_EMAIL

The email address to send form submissions to.

| Question | Answer |
| ------------ | ----------------- |
| Required | Yes |
| Sample Value | `me@mywebapp.org` |

### ALLOWED_ORIGINS

An optional comma-separated list of allowed origins for CORS (defaults to `*`). This is an important security measure to prevent malicious users from abusing your function.

| Question | Answer |
| ------------- | ------------------------------------------------------------------- |
| Required | No |
| Sample Value | `https://mywebapp.org,https://mywebapp.com` |
| Documentation | [MDN: CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) |
4 changes: 4 additions & 0 deletions java/email-contact-form/deps.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dependencies {
implementation 'io.appwrite:sdk-for-kotlin:4.0.0'
implementation 'com.sun.mail:jakarta.mail:2.0.0'
}
55 changes: 55 additions & 0 deletions java/email-contact-form/src/Cors.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.openruntimes.java.src;

import io.openruntimes.java.RuntimeContext;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.lang.*;


public class Cors {

/*
* Returns true if the origin is allowed to make requests to this endpoint
*
* Parameters:
* req: Request object
*
* Returns:
* (boolean): True if the origin is allowed, False otherwise
*/
public static boolean isOriginPermitted(RuntimeContext context) {
String allowedOrigins = System.getenv("ALLOWED_ORIGINS");
if (allowedOrigins == null || allowedOrigins.equals("*")) {
return true;
}

List<String> allowedOriginsList = Arrays.asList(allowedOrigins.split(","));
String originHeader = context.getReq().getHeaders().get("Origin");
return originHeader != null && allowedOriginsList.contains(originHeader);
}

/*
* Returns the CORS headers for the request
*
* Parameters:
* req: Request object
*
* Returns:
* (Map<String, String>): CORS headers
*/
public static Map<String, String> getCorsHeaders(RuntimeContext context) {
if (!context.getReq().getHeaders().containsKey("origin")) {
return new HashMap<>();
}

String allowedOrigins = System.getenv("ALLOWED_ORIGINS");
if (allowedOrigins == null || allowedOrigins.equals("*")) {
return new HashMap<>(Map.of("Access-Control-Allow-Origin", "*"));
}

String originHeader = context.getReq().getHeaders().get("origin");
return new HashMap<>(Map.of("Access-Control-Allow-Origin", originHeader));
}
}
7 changes: 7 additions & 0 deletions java/email-contact-form/src/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.openruntimes.java.src;

public class ErrorCode {
public static final String INVALID_REQUEST = "invalid-request";
public static final String MISSING_FORM_FIELDS = "missing-form-fields";
public static final String SERVER_ERROR = "server-error";
}
133 changes: 133 additions & 0 deletions java/email-contact-form/src/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package io.openruntimes.java.src;

import io.openruntimes.java.RuntimeContext;
import io.openruntimes.java.RuntimeOutput;
import java.util.Map;
import java.util.List;
import java.util.HashMap;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.net.URI;
import java.net.URISyntaxException;
import java.lang.*;
import io.appwrite.Client;
import jakarta.mail.MessagingException;


public class Main {

public RuntimeOutput main(RuntimeContext context) throws Exception {

List<String> requiredEnvVariables = Arrays.asList(
"SUBMIT_EMAIL",
"SMTP_HOST",
"SMTP_USERNAME",
"SMTP_PASSWORD"
);

Utils.throwIfMissing(System.getenv(), requiredEnvVariables);
if (System.getenv("ALLOWED_ORIGINS").equals("*")) {
context.log("WARNING: Allowing requests from any origin - this is a security risk!");
}

if (context.getReq().getMethod().equals("GET")) {
return context.getRes().send(
Utils.getHtmlContent("index.html"),
200,
Map.of("content-type", "text/html")
);
}


if(!context.getReq().getHeaders().get("content-type").equals("application/x-www-form-urlencoded")){
context.error("Incorrect content type");
String referer = context.getReq().getHeaders().get("referer");
String errorCode = ErrorCode.INVALID_REQUEST;
return context.getRes().redirect(
String.format("%s?code=%s", referer, errorCode)
);
}

if(!Cors.isOriginPermitted(context)){
context.error("Origin not permitted");
String referer = context.getReq().getHeaders().get("referer");
String errorCode = ErrorCode.INVALID_REQUEST;
return context.getRes().redirect(
String.format("%s?code=%s", referer, errorCode)
);
}

Map<String, List<String>> formData = new HashMap<>();
String body = (String) context.getReq().getBody();
String[] params = body.split("&");
for (String param : params) {
String[] keyValue = param.split("=");
String key = keyValue[0];
String value = keyValue[1];
if (!formData.containsKey(key)) {
formData.put(key, new ArrayList<>());
}
formData.get(key).add(value);
}

Map<String, String> form = new HashMap<>();
for (Map.Entry<String, List<String>> entry : formData.entrySet()) {
form.put(entry.getKey(), entry.getValue().get(0));
}

try {
Utils.throwIfMissing(form, Collections.singletonList("email"));
} catch (IllegalArgumentException ex) {
String referer = context.getReq().getHeaders().get("referer");
String errorCode = ErrorCode.MISSING_FORM_FIELDS;
return context.getRes().redirect(
String.format("%s?code=%s", referer, errorCode),
301,
Cors.getCorsHeaders(context)
);
}


try {
Map<String, String> emailOptions = new HashMap<>();
emailOptions.put("from", System.getenv("SMTP_USERNAME"));
emailOptions.put("to", System.getenv("SUBMIT_EMAIL"));
emailOptions.put("subject", "New Contact Form Submission");
emailOptions.put("text", Utils.templateFormMessage(form));
Utils.sendEmail(emailOptions);
} catch (MessagingException ex) {
context.log("MessagingException: " + ex.getMessage());
String referer = context.getReq().getHeaders().get("referer");
String errorCode = ErrorCode.SERVER_ERROR;
return context.getRes().redirect(
String.format("%s?code=%s", referer, errorCode),
301,
Cors.getCorsHeaders(context)
);
} catch (Exception ex) {
context.log("Exception: " + ex.getMessage());
String referer = context.getReq().getHeaders().get("referer");
String errorCode = ErrorCode.SERVER_ERROR;
return context.getRes().redirect(
String.format("%s?code=%s", referer, errorCode),
301,
Cors.getCorsHeaders(context)
);
}

if (form.get("_next") == null || form.get("_next").isEmpty()) {
return context.getRes().send(
Utils.getHtmlContent("success.html"),
200,
Map.of("content-type", "text/html; charset=utf-8")
);
}

return context.getRes().redirect(
Utils.joinURL(context.getReq().getHeaders().get("referer"), form.get("_next").substring(0,1)),
301,
Cors.getCorsHeaders(context)
);
}
}

0 comments on commit b143f3e

Please sign in to comment.