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.
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.
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.
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.
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.
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.