diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index beb9074..12776e1 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,2 +1,2 @@
# Global rule:
-* @Bandwidth-Samples/dx
+* @Bandwidth-Samples/band-swi @Bandwidth-Samples/band-devx
diff --git a/.github/actions/maven-test/Dockerfile b/.github/actions/maven-test/Dockerfile
deleted file mode 100644
index 8f75cd0..0000000
--- a/.github/actions/maven-test/Dockerfile
+++ /dev/null
@@ -1,11 +0,0 @@
-# Container image that runs your code
-FROM maven:3.6.2-jdk-14
-
-# Copies your code file from your action repository to the filesystem path `/` of the container
-COPY entrypoint.sh /entrypoint.sh
-
-#Make entrypoint.sh exacutable
-RUN chmod +x /entrypoint.sh
-
-# Code file to execute when the docker container starts up (`entrypoint.sh`)
-ENTRYPOINT ["/entrypoint.sh"]
diff --git a/.github/actions/maven-test/action.yml b/.github/actions/maven-test/action.yml
deleted file mode 100644
index 598f220..0000000
--- a/.github/actions/maven-test/action.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-# action.yml
-name: 'Test'
-description: 'Runs Maven test command'
-runs:
- using: 'docker'
- image: 'Dockerfile'
diff --git a/.github/actions/maven-test/entrypoint.sh b/.github/actions/maven-test/entrypoint.sh
deleted file mode 100644
index 91f2160..0000000
--- a/.github/actions/maven-test/entrypoint.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-mvn test
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..78cbe5d
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,19 @@
+version: 2
+
+updates:
+ - package-ecosystem: maven
+ directory: /
+ schedule:
+ interval: monthly
+ open-pull-requests-limit: 5
+ ignore:
+ - dependency-name: "com.bandwidth.sdk:bandwidth-sdk"
+ update-types:
+ - version-update:semver-minor
+ - version-update:semver-patch
+ groups:
+ dependencies:
+ patterns:
+ - "*"
+ exclude-patterns:
+ - "com.bandwidth.sdk:bandwidth-sdk"
diff --git a/.github/workflows/on-pull-request.yaml b/.github/workflows/on-pull-request.yaml
deleted file mode 100644
index 17573fb..0000000
--- a/.github/workflows/on-pull-request.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-name: Test
-
-on:
- pull_request:
- branches:
- - main
-
-jobs:
- maven-test:
- name: Test
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v1
- - name: Test
- uses: ./.github/actions/maven-test
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..de5c0cb
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,16 @@
+name: Test
+
+on:
+ pull_request:
+ branches:
+ - main
+ schedule:
+ - cron: "0 9 * * 1"
+
+jobs:
+ contract-test:
+ name: Contract Test
+ uses: Bandwidth-Samples/sample-app-tooling/.github/workflows/java.yml@main
+ with:
+ sample: mfa-cli
+ start-command: mvn -B -q exec:java
diff --git a/LICENSE b/LICENSE
index df9461d..ddc77f4 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020 Bandwidth Samples
+Copyright (c) 2026 Bandwidth Samples
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 106c8d5..a0830ac 100644
--- a/README.md
+++ b/README.md
@@ -13,37 +13,41 @@
# Description
-This app creates a simple CLI tool used to create and verify multi-factor auth codes using Bandwidth's Multi-Factor Auth API. The app will prompt the user for their phone number, followed by their preferred method of code delivery; either messaging or voice. The app will then text or call the phone number provided with a 6 digit MFA code that the user can enter back into the CLI to verify.
+This sample app is a command line tool that sends and verifies multi-factor auth codes using Bandwidth's Multi-Factor Auth API.
+
+The app asks for a phone number in E.164 format and whether to send the code by `messaging` or `voice` (`messaging` if left blank). It then texts or calls that number with a 6 digit code, asks you to enter the code, and checks it. The app exits with status `0` if the code is verified and `1` otherwise.
# Pre-Requisites
-In order to use the Bandwidth API users need to set up the appropriate application at the [Bandwidth Dashboard](https://dashboard.bandwidth.com/) and create API tokens.
+In order to use the Bandwidth API users need to set up the appropriate applications at the [Bandwidth Dashboard](https://dashboard.bandwidth.com/) and create API tokens.
-To create an application log into the [Bandwidth Dashboard](https://dashboard.bandwidth.com/) and navigate to the `Applications` tab. Fill out the **New Application** form selecting the service (Messaging or Voice) that the application will be used for. All Bandwidth services require publicly accessible Callback URLs, for more information on how to set one up see [Callback URLs](#callback-urls).
+This app needs two applications, one for each delivery method. To create an application log into the [Bandwidth Dashboard](https://dashboard.bandwidth.com/) and navigate to the `Applications` tab. Fill out the **New Application** form once selecting Messaging, and once selecting Voice. This app doesn't receive any callbacks, so the callback URLs can be left as placeholders.
For more information about API credentials see our [Account Credentials](https://dev.bandwidth.com/docs/account/credentials) page.
# Running the Application
-To install the required packages for this app, run the command:
+To install the required packages and build the app, run the command:
```sh
-mvn clean install
+mvn package
```
Use the following command to run the application:
```sh
-mvn exec:java
+mvn -q exec:java
```
# Environmental Variables
+
The sample app uses the below environmental variables.
+
```sh
-BW_ACCOUNT_ID # Your Bandwidth Account Id
-BW_USERNAME # Your Bandwidth API Token
-BW_PASSWORD # Your Bandwidth API Secret
-BW_NUMBER # Your The Bandwidth Phone Number
-BW_VOICE_APPLICATION_ID # Your Voice Application Id created in the dashboard
-BW_MESSAGING_APPLICATION_ID # Your Messaging Application Id created in the dashboard
+BW_ACCOUNT_ID # Your Bandwidth Account Id
+BW_CLIENT_ID # Your Bandwidth API OAuth Client Id
+BW_CLIENT_SECRET # Your Bandwidth API OAuth Client Secret
+BW_NUMBER # The Bandwidth phone number the code is sent from
+BW_VOICE_APPLICATION_ID # Your Voice Application Id created in the dashboard
+BW_MESSAGING_APPLICATION_ID # Your Messaging Application Id created in the dashboard
```
diff --git a/pom.xml b/pom.xml
index c279a1c..c16ac2f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,8 +11,7 @@
UTF-8UTF-8
- 11
- 2.2.6.RELEASE
+ 21
@@ -20,13 +19,7 @@
com.bandwidth.sdkbandwidth-sdk
- 9.1.2
-
-
-
- javax.activation
- javax.activation-api
- 1.2.0
+ 18.3.1
@@ -35,16 +28,16 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 3.8.1
+ 3.16.0
- ${java.version}
- ${java.version}
+ ${java.version}
+ ${java.version}
-
+
org.codehaus.mojoexec-maven-plugin
- 3.1.1
+ 3.6.4com.bandwidth.Main
diff --git a/src/main/java/com/bandwidth/Main.java b/src/main/java/com/bandwidth/Main.java
index 0bfb9a0..d778552 100644
--- a/src/main/java/com/bandwidth/Main.java
+++ b/src/main/java/com/bandwidth/Main.java
@@ -1,145 +1,96 @@
package com.bandwidth;
-import com.bandwidth.sdk.ApiResponse;
-import com.bandwidth.sdk.ApiException;
import com.bandwidth.sdk.ApiClient;
-import com.bandwidth.sdk.auth.HttpBasicAuth;
-import com.bandwidth.sdk.model.*;
-import com.bandwidth.sdk.api.*;
-import com.bandwidth.sdk.Configuration;
+import com.bandwidth.sdk.api.MfaApi;
+import com.bandwidth.sdk.model.CodeRequest;
+import com.bandwidth.sdk.model.VerifyCodeRequest;
-import java.math.BigDecimal;
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.util.Scanner;
+import java.math.BigDecimal;
public class Main {
- private static final String username = System.getenv("BW_USERNAME");
- private static final String password = System.getenv("BW_PASSWORD");
- private static final String accountId = System.getenv("BW_ACCOUNT_ID");
- private static final String voiceApplicationId = System.getenv("BW_VOICE_APPLICATION_ID");
- private static final String messagingApplicationId = System.getenv("BW_MESSAGING_APPLICATION_ID");
- private static final String bandwidthNumber = System.getenv("BW_NUMBER");
-
- public static ApiClient defaultClient = Configuration.getDefaultApiClient();
- public static HttpBasicAuth Basic = (HttpBasicAuth) defaultClient.getAuthentication("Basic");
- public static MfaApi api = new MfaApi(defaultClient);
-
- public static void main(String[] args) throws IOException {
-
- CodeRequest request = new CodeRequest();
- Scanner scanner = new Scanner(new InputStreamReader(System.in));
-
- System.out.println("Send the code to the following phone number (E164 format: +13330004444): ");
- String toNumber = scanner.nextLine();
+ private static final String SCOPE = "sample";
- System.out.println("Text or Voice? ");
- String option = scanner.nextLine();
+ private static final BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
- boolean success;
- String applicationId = null;
- if ("text".equalsIgnoreCase(option)) {
- success = sendMessageCode(toNumber);
- } else if ("voice".equalsIgnoreCase(option)) {
- success = sendVoiceCode(toNumber);
- } else {
- System.out.println("Must be 'Voice' or 'text'");
- return;
+ private static String prompt(String text) throws IOException {
+ System.out.print(text);
+ System.out.flush();
+ String line = stdin.readLine();
+ if (line == null) {
+ System.exit(1);
}
+ return line.strip();
+ }
- if (!success) {
- System.out.println("There was an error during code creation.");
- return;
+ public static void main(String[] args) throws IOException {
+ String accountId = System.getenv("BW_ACCOUNT_ID");
+ String clientId = System.getenv("BW_CLIENT_ID");
+ String clientSecret = System.getenv("BW_CLIENT_SECRET");
+ String number = System.getenv("BW_NUMBER");
+ String voiceApplicationId = System.getenv("BW_VOICE_APPLICATION_ID");
+ String messagingApplicationId = System.getenv("BW_MESSAGING_APPLICATION_ID");
+ if (accountId == null || clientId == null || clientSecret == null || number == null
+ || voiceApplicationId == null || messagingApplicationId == null) {
+ System.err.println("Please set the environmental variables defined in the README");
+ System.exit(1);
}
- System.out.println("What is the Code? ");
- String code = scanner.nextLine();
+ MfaApi mfaApi = new MfaApi(new ApiClient(clientId, clientSecret, null));
- BigDecimal expirationTime = new BigDecimal(3);
-
- VerifyCodeRequest verifyRequest = new VerifyCodeRequest();
- verifyRequest.setCode(code);
- verifyRequest.setTo(toNumber);
- verifyRequest.setScope("mfa");
- verifyRequest.setExpirationTimeInMinutes(expirationTime);
- ApiResponse response = null;
- try {
- response = api.verifyCodeWithHttpInfo(accountId, verifyRequest);
-
- } catch (ApiException e) {
- System.out.println(e.getMessage());
- return;
+ String phoneNumber = prompt("Enter the phone number to send the code to (E.164, e.g. +15554443333): ");
+ if (!phoneNumber.matches("\\+[1-9]\\d{1,14}")) {
+ System.err.println("Phone number must be in E.164 format, e.g. +15554443333");
+ System.exit(1);
}
- if (Boolean.TRUE.equals(response.getData().getValid())) {
- System.out.println("Valid Code!");
- } else {
- System.out.println("Invalid Code");
+ String method = prompt("Send the code by messaging or voice? [messaging]: ").toLowerCase();
+ if (method.isEmpty()) {
+ method = "messaging";
+ }
+ if (!method.equals("messaging") && !method.equals("voice")) {
+ System.err.println("Method must be messaging or voice");
+ System.exit(1);
}
- scanner.close();
- return;
- }
-
- public static boolean sendMessageCode(String toNumber) throws IOException {
-
- Basic.setUsername(username);
- Basic.setPassword(password);
- ApiResponse response = null;
- CodeRequest request = new CodeRequest();
- request.setTo(toNumber);
- request.setFrom(bandwidthNumber);
- request.setApplicationId(messagingApplicationId);
- request.setScope("mfa");
- request.setMessage("Your temporary {NAME} {SCOPE} code is {CODE}");
- request.setDigits(5);
+ CodeRequest codeRequest = new CodeRequest()
+ .to(phoneNumber)
+ .from(number)
+ .applicationId(method.equals("voice") ? voiceApplicationId : messagingApplicationId)
+ .scope(SCOPE)
+ .message("Your temporary {NAME} {SCOPE} code is {CODE}")
+ .digits(6);
+ System.out.println("Sending a code by " + method + " to " + phoneNumber);
try {
- response = api.generateMessagingCodeWithHttpInfo(accountId, request);
- } catch (ApiException e) {
- System.out.println(e.getMessage());
- return false;
- }
-
- if (response == null ) {
- System.out.println("Response was null");
- return false;
+ if (method.equals("voice")) {
+ mfaApi.generateVoiceCode(accountId, codeRequest);
+ } else {
+ mfaApi.generateMessagingCode(accountId, codeRequest);
+ }
+ } catch (Exception e) {
+ System.err.println("Failed to send code: " + e.getMessage());
+ System.exit(1);
}
- return true;
- }
-
- public static boolean sendVoiceCode(String toNumber) throws IOException {
-
- Basic.setUsername(username);
- Basic.setPassword(password);
- ApiResponse response = null;
- CodeRequest request = new CodeRequest();
- request.setTo(toNumber);
- request.setFrom(bandwidthNumber);
- request.setApplicationId(voiceApplicationId);
- request.setScope("mfa");
- request.setMessage("This is your {CODE}");
- request.setDigits(5);
+ VerifyCodeRequest verifyRequest = new VerifyCodeRequest()
+ .to(phoneNumber)
+ .scope(SCOPE)
+ .expirationTimeInMinutes(new BigDecimal(3))
+ .code(prompt("Enter the code you received: "));
+ boolean valid = false;
try {
- response = api.generateVoiceCodeWithHttpInfo(accountId, request);
- } catch (ApiException e) {
- System.out.println(e.getMessage());
- return false;
+ valid = Boolean.TRUE.equals(mfaApi.verifyCode(accountId, verifyRequest).getValid());
+ } catch (Exception e) {
+ System.err.println("Failed to verify code: " + e.getMessage());
+ System.exit(1);
}
- if (response == null ) {
- System.out.println("Response was null");
- return false;
- }
-
- return true;
+ System.out.println(valid ? "Code verified" : "Code not verified");
+ System.exit(valid ? 0 : 1);
}
-
-
-
-
-
}