Skip to content

Commit

Permalink
Merge pull request #45 from appwrite/feat-port-censor-with-redact
Browse files Browse the repository at this point in the history
feat: Port Censor with Redact
  • Loading branch information
loks0n authored Aug 29, 2023
2 parents 94ae150 + 75859b5 commit 2913e88
Show file tree
Hide file tree
Showing 13 changed files with 632 additions and 0 deletions.
27 changes: 27 additions & 0 deletions dart/censor_with_redact/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# See https://www.dartlang.org/guides/libraries/private-files

# Files and directories created by pub
.dart_tool/
.packages
build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock

# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/

# dotenv environment variables file
.env*

# Avoid committing generated Javascript files:
*.dart.js
*.info.json # Produced by the --dump-info flag.
*.js # When generated by dart2js. Don't specify *.js if your
# project includes source files written in JavaScript.
*.js_
*.js.deps
*.js.map

.flutter-plugins
.flutter-plugins-dependencies
62 changes: 62 additions & 0 deletions dart/censor_with_redact/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# 🤐 Dart Censor with Redact Function

Automatically remove sensitive data from messages.

## 🧰 Usage

### GET /

HTML form for interacting with the function.

### POST /

Returns the supplied text string with sensitive information redacted.

**Parameters**

| Name | Description | Location | Type | Sample Value |
| ------------ | --------------------------- | -------- | ------------------ | ------------------------------------------ |
| Content-Type | Content type of the request | Header | `application/json` | N/A |
| text | Text to redact | Body | String | `My email address is [email protected]` |

**Response**

Sample `200` Response:

```json
{
"ok": true,
"redacted": "My email address is <EMAIL_ADDRESS>"
}
```

Sample `400` Response:

```json
{
"ok": false,
"error": "Missing required field: text."
}
```

## ⚙️ Configuration

| Setting | Value |
| ----------------- | --------------- |
| Runtime | Dart (2.17) |
| Entrypoint | `lib/main.dart` |
| Build Commands | `dart pub get` |
| Permissions | `any` |
| Timeout (Seconds) | 15 |

## 🔒 Environment Variables

### PANGEA_REDACT_TOKEN

Access token for the Pangea Redact API

| Question | Answer |
| ------------- | --------------------------------------------------------------------------------------- |
| Required | Yes |
| Sample Value | `pts_7p4...5wl4` |
| Documentation | [Pangea: Configuration](https://pangea.cloud/docs/redact/getting-started/configuration) |
1 change: 1 addition & 0 deletions dart/censor_with_redact/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:lints/recommended.yaml
36 changes: 36 additions & 0 deletions dart/censor_with_redact/lib/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'utils.dart';

Future<dynamic> main(final context) async {
throwIfMissing(Platform.environment, ['PANGEA_REDACT_TOKEN']);

if (context.req.method == 'GET') {
return context.res.send(getStaticFile('index.html'), 200,
{'Content-Type': 'text/html; charset=utf-8'});
}

try {
throwIfMissing(context.req.body, ['text']);
} catch (err) {
return context.res.json({'ok': false, 'error': err.toString()});
}

final response =
await http.post(Uri.parse('https://redact.aws.eu.pangea.cloud/v1/redact'),
headers: {
'Content-Type': 'application/json',
'Authorization':
'Bearer ${Platform.environment['PANGEA_REDACT_TOKEN']}',
},
body: jsonEncode({
'text': context.req.body['text'],
}));

final data = jsonDecode(response.body);

return context.res
.json({'ok': true, 'redacted': data['result']['redacted_text']});
}
24 changes: 24 additions & 0 deletions dart/censor_with_redact/lib/utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'dart:io';

final String _dirname = Platform.script.toFilePath();
final String staticFolder = '${Uri.file(_dirname).resolve('../static')}';

/// Throws an error if any of the keys are missing from the object
void throwIfMissing(Map<String, dynamic> obj, List<String> keys) {
final missing = <String>[];
for (var key in keys) {
if (!obj.containsKey(key) || obj[key] == null) {
missing.add(key);
}
}
if (missing.isNotEmpty) {
throw Exception('Missing required fields: ${missing.join(', ')}');
}
}

/// Returns the contents of a file in the static folder
/// @param fileName - The name of the file to read
/// @returns Contents of static/{fileName}
String getStaticFile(String fileName) {
return File('$staticFolder/$fileName').readAsStringSync();
}
11 changes: 11 additions & 0 deletions dart/censor_with_redact/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: censor_with_redact
version: 1.0.0

environment:
sdk: ^2.17.0


dev_dependencies:
lints: ^2.0.0
dependencies:
http: ^1.1.0
91 changes: 91 additions & 0 deletions dart/censor_with_redact/static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Censor with Redact API Demo</title>

<script>
async function onSubmit(text) {
const response = await fetch('/', {
method: 'POST',
body: JSON.stringify({ text }),
headers: {
'Content-Type': 'application/json',
},
});

const json = await response.json();

if (!json.ok || json.error) {
alert(json.error);
}

return json.redacted;
}
</script>

<script src="//unpkg.com/alpinejs" defer></script>

<link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
<link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
</head>
<body>
<main class="main-content">
<div class="top-cover u-padding-block-end-56">
<div class="container">
<div
class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16"
>
<h1 class="heading-level-1">Censor with Redact API Demo</h1>
<code class="u-un-break-text"></code>
</div>
<p
class="body-text-1 u-normal u-margin-block-start-8"
style="max-width: 50rem"
>
Use this page to test your implementation with Redact API. Enter
text and receive the censored message as a response.
</p>
</div>
</div>
<div
class="container u-margin-block-start-negative-56"
x-data="{ message: '', censoredMessage: '', loading: false }"
>
<div class="card u-flex u-gap-24 u-flex-vertical">
<div class="u-flex u-cross-center u-gap-8">
<div
class="input-text-wrapper is-with-end-button u-width-full-line"
>
<input x-model="message" type="search" placeholder="Message" />
<div class="icon-search" aria-hidden="true"></div>
</div>

<button
class="button"
x-bind:disabled="loading"
x-on:click="async () => { loading = true; censoredMessage = ''; try { censoredMessage = await onSubmit(message) } catch(err) { console.error(err); } finally { loading = false; } }"
>
<span class="text">Censor</span>
</button>
</div>
<template x-if="censoredMessage">
<div class="u-flex u-flex-vertical u-gap-12">
<div class="u-flex u-flex-vertical u-gap-12 card">
<div class="u-flex u-gap-12">
<h5 class="eyebrow-heading-2">Redact API:</h5>
</div>

<div style="overflow-x: hidden; line-break: anywhere">
<p class="u-color-text-gray" x-text="censoredMessage"></p>
</div>
</div>
</div>
</template>
</div>
</div>
</main>
</body>
</html>
Loading

0 comments on commit 2913e88

Please sign in to comment.