-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from wneessen/v0.1.3_reCaptcha
v0.1.3: Added reCaptcha v2 support
- Loading branch information
Showing
5 changed files
with
148 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,10 +2,11 @@ | |
|
||
[![Go Report Card](https://goreportcard.com/badge/github.com/wneessen/js-mailer)](https://goreportcard.com/report/github.com/wneessen/js-mailer) [![Build Status](https://api.cirrus-ci.com/github/wneessen/js-mailer.svg)](https://cirrus-ci.com/github/wneessen/js-mailer) | ||
|
||
JS-Mailer is a simple webservice, that allows JavaScript-based websites to easily send form data, by providing a | ||
simple API that can be accessed via JavaScript `Fetch()` or `XMLHttpRequest`. | ||
JS-Mailer is a simple webservice, that allows JavaScript-based websites to easily send form data, by providing a simple | ||
API that can be accessed via JavaScript `Fetch()` or `XMLHttpRequest`. | ||
|
||
## Features | ||
|
||
* Single-binary webservice | ||
* Multi-form support | ||
* Multiple recipients per form | ||
|
@@ -16,11 +17,12 @@ simple API that can be accessed via JavaScript `Fetch()` or `XMLHttpRequest`. | |
* Limit form access to specific domains | ||
* Per-form mail server configuration | ||
* hCaptcha support | ||
* reCaptcha v2 support | ||
|
||
### Planed features | ||
|
||
* [ ] Form field-type validation | ||
* [ ] Form body templates (possibly HTML) | ||
* [ ] gCaptcha support | ||
|
||
## Installation | ||
|
||
|
@@ -35,8 +37,9 @@ There is a ready-to-use Docker image hosted on Github. | |
```shell | ||
$ docker run -p 8765:8765 -v /etc/js-mailer:/etc/js-mailer ghcr.io/wneessen/js-mailer:main | ||
``` | ||
|
||
## Configuration | ||
|
||
### Server configuration | ||
|
||
The server configuration, by default, is searched for in `/etc/js-mailer/js-mailer.json`. The JSON syntax is very basic | ||
|
@@ -57,33 +60,50 @@ and comes with sane defaults. | |
``` | ||
|
||
* `api (type: struct)`: The struct for the web api configuration | ||
* `bind_addr (type: string)`: The IP address to bind the web service to | ||
* `port (type: uint)`: The port for the webservice to listen on | ||
* `bind_addr (type: string)`: The IP address to bind the web service to | ||
* `port (type: uint)`: The port for the webservice to listen on | ||
* `forms (type: struct)`: The struct for the forms configuration | ||
* `path (type: string)`: The path in which `js-mailer` will look for form configuration JSON files | ||
* `maxlength (type: int64)`: Maximum length in bytes of memory that will be read from the form data HTTP header | ||
* `loglevel (type: string)`: The log level for the web service | ||
|
||
### Form configuration | ||
Each form has its own configuration file. The configuration is searched in the forms path and are named by its id. | ||
Again the JSON syntax of the form configuration is very simple, yet flexible. | ||
|
||
Each form has its own configuration file. The configuration is searched in the forms path and are named by its id. Again | ||
the JSON syntax of the form configuration is very simple, yet flexible. | ||
|
||
```json | ||
{ | ||
"id": "test_form", | ||
"secret": "SuperSecretsString", | ||
"recipients": ["[email protected]"], | ||
"recipients": [ | ||
"[email protected]" | ||
], | ||
"sender": "[email protected]", | ||
"domains": ["www.example.com", "example.com"], | ||
"domains": [ | ||
"www.example.com", | ||
"example.com" | ||
], | ||
"content": { | ||
"subject": "New message through the www.example.com contact form", | ||
"fields": ["name", "email", "message"], | ||
"required_fields": ["name", "email"], | ||
"fields": [ | ||
"name", | ||
"email", | ||
"message" | ||
], | ||
"required_fields": [ | ||
"name", | ||
"email" | ||
], | ||
"honeypot": "street" | ||
}, | ||
"hcaptcha": { | ||
"enabled": true, | ||
"secret_key": "0x01234567890" | ||
"enabled": true, | ||
"secret_key": "0x01234567890" | ||
}, | ||
"recaptcha": { | ||
"enabled": true, | ||
"secret_key": "0x01234567890" | ||
}, | ||
"server": { | ||
"host": "mail.example.com", | ||
|
@@ -95,25 +115,29 @@ Again the JSON syntax of the form configuration is very simple, yet flexible. | |
} | ||
} | ||
``` | ||
|
||
* `id (type: string)`: The id of the form (will be looked for in the `formid` parameter of the token request) | ||
* `secret (type: string)`: Secret for the form. This will be used for the token generation | ||
* `recipients (type: []string)`: List of recipients, that should receive the mails with the submitted form data | ||
* `domains (type: []string)`: List of origin domains, that are allowed to use this form | ||
* `content (type: struct)`: The struct for the mail content configuration | ||
* `subject (type: string)`: Subject for the mail notification of the form submission | ||
* `fields (type: []string)`: List of field names that should show up in the mail notification | ||
* `required_fields (type: []string)`: List of field names that are required to submitted | ||
* `honeypot (type: string)`: Name of the honeypot field, that is expected to be empty (Anti-SPAM) | ||
* `subject (type: string)`: Subject for the mail notification of the form submission | ||
* `fields (type: []string)`: List of field names that should show up in the mail notification | ||
* `required_fields (type: []string)`: List of field names that are required to submitted | ||
* `honeypot (type: string)`: Name of the honeypot field, that is expected to be empty (Anti-SPAM) | ||
* `hcaptcha (type: struct)`: The struct for the forms hCaptcha configuration | ||
* `enabled (type: bool)`: Enable hCaptcha challenge-response validation | ||
* `secret_key (type: string)`: Your hCaptcha secret key | ||
* `enabled (type: bool)`: Enable hCaptcha challenge-response validation | ||
* `secret_key (type: string)`: Your hCaptcha secret key | ||
* `recaptcha (type: struct)`: The struct for the forms reCaptcha configuration | ||
* `enabled (type: bool)`: Enable reCaptcha challenge-response validation | ||
* `secret_key (type: string)`: Your reCaptcha secret key | ||
* `server (type: struct)`: The struct for the forms mail server configuration | ||
* `host (type: string)`: Hostname of the sending mail server | ||
* `port (type: uint32)`: Port to connect to on the sending mail server | ||
* `username (type: string)`: Username for the mail server authentication | ||
* `password (type: string)`: Password for the mail server authentication | ||
* `timeout (type: duration)`: Timeout duration for the mail server connection | ||
* `force_tls (type: boolean)`: If set to true, the mail server connection will require mandatory TLS | ||
* `host (type: string)`: Hostname of the sending mail server | ||
* `port (type: uint32)`: Port to connect to on the sending mail server | ||
* `username (type: string)`: Username for the mail server authentication | ||
* `password (type: string)`: Password for the mail server authentication | ||
* `timeout (type: duration)`: Timeout duration for the mail server connection | ||
* `force_tls (type: boolean)`: If set to true, the mail server connection will require mandatory TLS | ||
|
||
## Workflow | ||
|
||
|
@@ -125,36 +149,41 @@ is submitted, the API will then validate that all submitted data is correct and | |
recipients. | ||
|
||
## API responses | ||
|
||
The API basically responds with two different types of JSON objects. A `success` response or an `error` response. | ||
|
||
### Success response | ||
|
||
The succss response JSON struct is very simple: | ||
|
||
```json | ||
{ | ||
"status_code": 200, | ||
"status": "Ok", | ||
"data": {} | ||
"status_code": 200, | ||
"status": "Ok", | ||
"data": {} | ||
} | ||
``` | ||
|
||
* `status_code (type: uint32)`: The HTTP status code of the success response | ||
* `status (type: string)`: The HTTP status string of the success response | ||
* `data (type: object)`: An object with abritrary data, based on the type of response | ||
|
||
#### Successful token retrieval data object | ||
|
||
The `data` object of the success response for a successful token retrieval looks like this: | ||
|
||
```json | ||
{ | ||
"token": "5b19fca2b154a2681f8d6014c63b5f81bdfdd01036a64f8a835465ab5247feff", | ||
"form_id": "test_form", | ||
"create_time": 1628670201, | ||
"expire_time": 1628670801, | ||
"url": "https://jsmailer.example.com/api/v1/send/test_form/5b19fca2b154a2681f8d6014c63b5f81bdfdd01036a64f8a835465ab5247feff", | ||
"enc_type": "multipart/form-data", | ||
"method": "post" | ||
"token": "5b19fca2b154a2681f8d6014c63b5f81bdfdd01036a64f8a835465ab5247feff", | ||
"form_id": "test_form", | ||
"create_time": 1628670201, | ||
"expire_time": 1628670801, | ||
"url": "https://jsmailer.example.com/api/v1/send/test_form/5b19fca2b154a2681f8d6014c63b5f81bdfdd01036a64f8a835465ab5247feff", | ||
"enc_type": "multipart/form-data", | ||
"method": "post" | ||
} | ||
``` | ||
|
||
* `token (type: string)`: The security token of this send request | ||
* `form_id (type: string)`: The form id of the current form (for reference or automatic inclusion via JS) | ||
* `create_time (type: int64)`: The epoch timestamp when the token was created | ||
|
@@ -164,29 +193,34 @@ The `data` object of the success response for a successful token retrieval looks | |
* `method (type: string)`: The method for your form | ||
|
||
#### Sent successful data object | ||
|
||
The `data` object of the success response for a successfully sent message looks like this: | ||
|
||
The API response to a send request (`/api/v1/send/<formid>/<token>`) looks like this: | ||
|
||
```json | ||
{ | ||
"form_id": "test_form", | ||
"send_time": 1628670331 | ||
"form_id": "test_form", | ||
"send_time": 1628670331 | ||
} | ||
``` | ||
|
||
* `form_id (type: string)`: The form id of the current form (for reference) | ||
* `send_time (type: int64)`: The epoch timestamp when the message was sent | ||
|
||
### Error response | ||
|
||
The error response JSON struct is also very simple: | ||
|
||
```json | ||
{ | ||
"status_code": 404, | ||
"status": "Not Found", | ||
"error_message": "Validation failed", | ||
"error_data": "Not a valid send URL" | ||
"status_code": 404, | ||
"status": "Not Found", | ||
"error_message": "Validation failed", | ||
"error_data": "Not a valid send URL" | ||
} | ||
``` | ||
|
||
* `status_code (type: uint32)`: The HTTP status code of the success response | ||
* `status (type: string)`: The HTTP status string of the success response | ||
* `error_message (type: string)`: The general error message why this request failed | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package validation | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
log "github.com/sirupsen/logrus" | ||
"net/http" | ||
"net/url" | ||
) | ||
|
||
// HcaptchaResponseJson reflect the API response from hCaptcha | ||
type RecaptchaResponseJson struct { | ||
Success bool `json:"success"` | ||
ChallengeTimestamp string `json:"challenge_ts"` | ||
Hostname string `json:"hostname"` | ||
} | ||
|
||
// RecaptchaValidate validates the reCaptcha challenge against the Google API | ||
func RecaptchaValidate(c, s string) bool { | ||
l := log.WithFields(log.Fields{ | ||
"action": "validation.RecaptchaValidate", | ||
}) | ||
|
||
// Create a HTTP request | ||
postData := url.Values{ | ||
"response": {c}, | ||
"secret": {s}, | ||
} | ||
httpResp, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", postData) | ||
if err != nil { | ||
l.Errorf("an error occurred creating new HTTP POST request: %v", err) | ||
return false | ||
} | ||
|
||
var respBody bytes.Buffer | ||
_, err = respBody.ReadFrom(httpResp.Body) | ||
if err != nil { | ||
l.Errorf("Failed to read response body: %s", err) | ||
return false | ||
} | ||
if httpResp.StatusCode == http.StatusOK { | ||
var recapResp RecaptchaResponseJson | ||
if err := json.Unmarshal(respBody.Bytes(), &recapResp); err != nil { | ||
l.Errorf("Failed to unmarshal response JSON: %s", err) | ||
return false | ||
} | ||
return recapResp.Success | ||
} | ||
|
||
return false | ||
} |