Moderate Chat Content with Swift on AWS Lambda

Matheus C.
Matheus C.
Published October 2, 2020 Updated April 29, 2022

Most of the time, when building a chat application, it's essential to have some level of control over what your users can share and say to each other.

In this tutorial, we'll use Swift Lambda and Stream's powerful chat API to build a content moderation system that can prevent users from sending unwanted content. In this case, we'll filter out possible credit card data to prevent our users from sharing it with scammers. However, you can adapt it to other sensitive data or to block bad words.

Animation shows a chat screen with a message containing credit card information being typed and then redacted after submission

You can find the completed project on GitHub in the Samples folder inside the Swift Lambda repository.

Requirements

Set the Presend Webhook URL

After you've built your iOS chat app and the Swift Lambda is up and running, you need to configure the Presend Webhook URL using the Stream Chat REST API. To do this, you'll need your API Key, a server-side JWT, and your AWS Lambda URL.

To get the API Key, go to the Stream Chat dashboard. It will be there, similar to the image below.

Image shows a Stream application with the API Key and Secret in the Stream Dashboard

Also, copy the secret, as it is needed to generate the server-side JWT. You can use jwt.io to generate it. Just paste the secret inside the text field on the right side and copy the JWT on the left side.

Image shows JWT.io website with arrows pointing to the generated JWT and the text field where the secret is pasted

Finally, to set the Presend Webhook URL, you can run the curl command below in the terminal. Make sure to replace [api_key], [jwt], and [aws_lambda_endpoint_url].

curl --location --request PATCH 'https://chat-us-east-1.stream-io-api.com/app?api_key=[api_key]' \
--header 'Stream-Auth-Type: jwt' \
--header 'Authorization: [jwt]' \
--header 'Content-Type: application/json' \
--data-raw '{"before_message_send_hook_url": "[aws_lambda_endpoint_url]"}'

Configure Swift Lambda

At first, your Swift Lambda will handle GET requests. Change it to process POST requests instead, which can be done by editing the serverless.yml file and swapping the line method: get with method: post.

Filter out credit card numbers

Now that the Swift Lambda is configured, we can get to the code part.

First, we need a function to redact credit card numbers from a message by replacing the numbers with asterisks. The code below does just that by using regular expressions to match valid numbers of known credit card operators.

func redactCreditCardNumbers(from text: String) -> String {
    var text = text
    
    let americanExpress = "(?:3[47][0-9]{13})";
    let dinersClub = "(?:3(?:0[0-5]|[68][0-9])[0-9]{11})";
    let discover = "(?:6(?:011|5[0-9]{2})(?:[0-9]{12}))";
    let jcb = "(?:(?:2131|1800|35\\d{3})\\d{11})";
    let maestro = "(?:(?:5[0678]\\d\\d|6304|6390|67\\d\\d)\\d{8,15})";
    let mastercard = "(?:(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12})";
    let visa = "(?:4[0-9]{12})(?:[0-9]{3})?";

    let all = [americanExpress, dinersClub, discover, jcb, maestro, mastercard, visa].joined(separator: "|")
    
    let regex = try! NSRegularExpression(pattern: all)

    let res = regex.matches(in: text, options: [], range: NSRange(location: 0, length: text.count))

    res.forEach {
        text = text.replacingCharacters(
            in: Range($0.range, in: text)!,
            with: String(repeating: "*", count: $0.range.length)
        )
    }
    
    return text
}

You should add that to the main.swift file.

Handle the message

When the POST request hits your lambda, it will contain a JSON object describing the new message. To see which fields you can expect in this payload, see the documentation.

In the main.swift file, paste the code below.

import AWSLambdaEvents
import AWSLambdaRuntime
import NIO

import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

Lambda.run { (context: Lambda.Context, event: APIGateway.Request, callback: @escaping (Result<APIGateway.Response, Error>) -> Void) in
    guard
        let data = event.body?.data(using: .utf8),
        let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
        var message = json["message"] as? [String: Any]
    else {
        callback(.success(APIGateway.Response(statusCode: .ok)))
        return
    }
    
    // rewrite message's text with redacted version
    message["text"] = redactCreditCardNumbers(from: text)
    
    let body = ["message": message]
    let bodyData = try! JSONSerialization.data(withJSONObject: body)
    let string = String(data: bodyData, encoding: .utf8)
    
    callback(.success(APIGateway.Response(statusCode: .ok, body: string)))
}

That code will parse the JSON body into a dictionary and modify the message object by running its "text" field through the credit card redaction function. As specified in the documentation, we return this modified message object to submit the redacted version.

Deploying and testing the content moderation

The main advantage of using Swift Lambda is that it's possible to iterate fast with it. After writing the moderation code, just run the ./Scripts/deploy.sh script again and wait a few seconds.

This animation shows the deploy script running and finishing successfully

After the upload is finished, you can run the chat app and type something in any channel. If it contains a seemingly valid credit card number such as 6011881485017922, it should be replaced with asterisks.

Image shows a chat UI running on iPhone simulator with a chat screen containing redacted messages

Next steps with Swift Lambda

Congratulations! You've just uploaded a real Swift backend to AWS. This moderation code is simple and made to demonstrate a usage of Swift Lambda to empower your chat apps. There are many ways to build even richer use cases with Swift Lambda. To find out more, check out the chatbot tutorial with Swift Lambda and Stream Chat's documentation for Swift.

Integrating Video With Your App?
We've built a Video and Audio solution just for you. Check out our APIs and SDKs.
Learn more ->