@@ -294,7 +405,16 @@ const LogInStep = function ({ navigation }) {
isInvalid={!isUsernameValid() && initialInput}
isValid={isUsernameValid()}
required
+ autoComplete="username webauthn"
/>
+ {/**Chrome has a requirement that a password field be present, will remove once it's no longer needed**/}
+
{errors.username}
diff --git a/clients/web/react/src/Passkey/InitUserStep.tsx b/clients/web/react/src/Passkey/InitUserStep.tsx
new file mode 100644
index 0000000..3a3857b
--- /dev/null
+++ b/clients/web/react/src/Passkey/InitUserStep.tsx
@@ -0,0 +1,45 @@
+import React, { useEffect } from "react";
+import { useDispatch, useSelector, RootStateOrAny } from "react-redux";
+import { Spinner } from "react-bootstrap";
+import { useTranslation } from "react-i18next";
+import { history } from "../_helpers";
+import { userActions } from "../_actions";
+
+const styles = require("../_components/component.module.css");
+
+/**
+ * Transitionary page that is used to log in the user and to set auth tokens used for APIs - This step should only be reached after a successful registration
+ * @returns User is routed back to the login screen, with all credentials removed from the browser
+ */
+const InitUserStep = function () {
+ const { t } = useTranslation();
+
+ const user = useSelector((state: RootStateOrAny) => state.users);
+
+ const dispatch = useDispatch();
+
+ /**
+ * Once a user is configured, ensure that they have a user token
+ * If the user has a token, allow them to proceed to the key registration success page
+ */
+ useEffect(() => {
+ dispatch(userActions.getCurrentAuthenticatedUser());
+ }, []);
+
+ useEffect(() => {
+ const token = user?.token;
+
+ if (token !== undefined) {
+ history.push("/");
+ }
+ }, [user]);
+
+ return (
+
+
+
{t("init-user")}
+
+ );
+};
+
+export default InitUserStep;
diff --git a/clients/web/react/src/Passkey/PasskeyLogin.jsx b/clients/web/react/src/Passkey/PasskeyLogin.jsx
new file mode 100644
index 0000000..3f8328b
--- /dev/null
+++ b/clients/web/react/src/Passkey/PasskeyLogin.jsx
@@ -0,0 +1,127 @@
+import React, { useState, useEffect, useCallback } from "react";
+
+import { WebAuthnClient } from "../_components";
+import { history } from "../_helpers";
+import { Auth } from "aws-amplify";
+import { get, create } from "@github/webauthn-json";
+import { Spinner } from "react-bootstrap";
+
+const styles = require("../_components/component.module.css");
+
+/**
+ * Step used to login the user with the autofill mechanism from Apple devices for Passkeys
+ */
+const PasskeyLogin = function ({ navigation }) {
+ const [autoComplete, setAC] = useState("");
+ const [loading, setLoading] = useState(false);
+
+ const mediationAvailable = () => {
+ const pubKeyCred = PublicKeyCredential;
+ // Check if the function exists on the browser - Not safe to assume as the page will crash if the function is not available
+ //typeof check is used as browsers that do not support mediation will not have the 'isConditionalMediationAvailable' method available
+ if (
+ typeof pubKeyCred.isConditionalMediationAvailable === "function" &&
+ pubKeyCred.isConditionalMediationAvailable()
+ ) {
+ console.log("Mediation is available");
+ return true;
+ }
+ console.log("Mediation is not available");
+ return false;
+ };
+
+ /**
+ * Function meant to prevent the form below from triggering a page refresh on submit
+ * @param event event triggered from the UI
+ */
+ const cO = (event) => {
+ event.preventDefault();
+ };
+
+ const passkeySignIn = useCallback(async () => {
+ console.log("In passkeySignIn");
+
+ try {
+ // Reaching out to Cognito for auth challenge
+ let requestOptions = await WebAuthnClient.getPublicKeyRequestOptions();
+ setAC("username webuathn");
+ console.log("Printing response from Cognito: ", requestOptions);
+
+ // Good news, webauthn-json still works with mediation (praise be for loose typing in JS)
+ const credential = await get({
+ publicKey: requestOptions.publicKeyCredentialRequestOptions,
+ mediation: "conditional",
+ });
+ setLoading(true);
+
+ console.log("Credential found for: ", credential.response.userHandle);
+ const name = credential.response.userHandle;
+ const cognitoUser = await Auth.signIn(name);
+ console.log("cognitoUser: ", cognitoUser);
+
+ const challengeResponse = {
+ credential: credential,
+ requestId: requestOptions.requestId,
+ pinCode: "-1",
+ };
+ const userData = await WebAuthnClient.sendChallengeAnswer(
+ cognitoUser,
+ challengeResponse,
+ "-1"
+ );
+ console.log(userData);
+ setLoading(false);
+ navigation.go("InitUserStep");
+ } catch (error) {
+ console.log(error);
+ }
+ }, []);
+
+ useEffect(() => {
+ if (!mediationAvailable()) {
+ history.push("/login");
+ } else {
+ setAC("");
+ passkeySignIn().catch(console.error);
+ }
+ }, [passkeySignIn]);
+
+ const signInStep = () => {
+ history.push("/login");
+ };
+
+ const signUpStep = () => {
+ history.push("/register");
+ };
+
+ return (
+ <>
+
Login with passkey
+
+
+ {loading && (
+
+
+
Authenticating
+
+ )}
+
+ Don't see a passkey?{" "}
+
+ Login another way
+
+
+
+ Don't have an account?{" "}
+
+ Sign Up
+
+
+ >
+ );
+};
+
+export default PasskeyLogin;
diff --git a/clients/web/react/src/Passkey/PasskeyLoginFlowPage.jsx b/clients/web/react/src/Passkey/PasskeyLoginFlowPage.jsx
new file mode 100644
index 0000000..ef2dcd6
--- /dev/null
+++ b/clients/web/react/src/Passkey/PasskeyLoginFlowPage.jsx
@@ -0,0 +1,9 @@
+import React from "react";
+
+import PasskeyLoginPage from "./PasskeyLoginPage";
+
+function PasskeyLoginFlowPage() {
+ return ;
+}
+
+export default PasskeyLoginFlowPage;
diff --git a/clients/web/react/src/Passkey/PasskeyLoginPage.jsx b/clients/web/react/src/Passkey/PasskeyLoginPage.jsx
new file mode 100644
index 0000000..7b1c3dd
--- /dev/null
+++ b/clients/web/react/src/Passkey/PasskeyLoginPage.jsx
@@ -0,0 +1,33 @@
+import React from "react";
+import { useStep } from "react-hooks-helper";
+
+import PasskeyLogin from "./PasskeyLogin";
+import InitUserStep from "./InitUserStep";
+
+const steps = [{ id: "PasskeyLogin" }, { id: "InitUserStep" }];
+
+function PasskeyLoginPage() {
+ const _initialStep = 0;
+ const { step, navigation } = useStep({ initialStep: _initialStep, steps });
+
+ const { id } = step;
+
+ const props = { navigation };
+
+ const renderSwitch = (id) => {
+ switch (id) {
+ // Step will be the default IF a trusted device is detected in the local application storage
+ case "PasskeyLogin":
+ return ;
+ // Primary login step, allowing the user to enter their username - Also allows for the use of recovery codes, and usernamless login
+ case "InitUserStep":
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ return <>{renderSwitch(id)}>;
+}
+
+export default PasskeyLoginPage;
diff --git a/scripts/Mac-Linux/Dockerfile b/scripts/Mac-Linux/Dockerfile
index ec5fe8a..f5293ce 100644
--- a/scripts/Mac-Linux/Dockerfile
+++ b/scripts/Mac-Linux/Dockerfile
@@ -1,7 +1,7 @@
# Pull in base image that already has Java, MVN, and Node pre-installed
FROM openkbs/jdk-mvn-py3
# The above Docker base image has the following pre-installed/configured:
-# node: v15.1.0 | npm: v7.0.8 | java: OpenJDK v1.8.0_265 | python: v3.6.9
+# node: v16.4.1 | npm: v7.20.0 | java: OpenJDK v11.0.11 | python: v3.6.9
# Terminal interaction
ENV TERM linux
@@ -13,7 +13,7 @@ RUN curl -S "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscl
#2 - Install AWS SAM CLI via Python3 (https://github.com/aws/aws-sam-cli/issues/1424)
RUN echo "Installing AWS SAM CLI..."
-RUN pip3 install --user --upgrade aws-sam-cli --no-warn-script-location --quiet > /dev/null 2>&1
+RUN curl -L https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip -o aws-sam-cli-linux-x86_64.zip && unzip aws-sam-cli-linux-x86_64.zip -d sam-installation && sudo ./sam-installation/install
#3 - Install webpack
RUN echo "Installing Webpack..."
diff --git a/scripts/Mac-Linux/deployStarterKit.sh b/scripts/Mac-Linux/deployStarterKit.sh
index 45a35c8..f2753f9 100755
--- a/scripts/Mac-Linux/deployStarterKit.sh
+++ b/scripts/Mac-Linux/deployStarterKit.sh
@@ -143,18 +143,18 @@ aws s3 mb s3://$S3_BUCKET_NAME --region $AWS_REGION --endpoint-url https://s3.$A
#3 |*************************** SAM Build ********************************************|
echo "Step 3 [Deployment] Running SAM build...(~1 minute) "
docker run -w /webauthnkit/backend --volume=$STARTER_KIT_DIR:/webauthnkit starterkit:dev \
-/home/developer/.local/bin/sam build > /dev/null 2>&1
+/usr/local/bin/sam build > /dev/null 2>&1
#4 |***************************** SAM Package ****************************************|
echo "Step 4 [Deployment] Running SAM package..."
docker run -w /webauthnkit/backend --volume=$STARTER_KIT_DIR:/webauthnkit starterkit:dev \
-/home/developer/.local/bin/sam package > /dev/null 2>&1
+/usr/local/bin/sam package > /dev/null 2>&1
#5 |**************************** SAM Deploy ******************************************|
echo "Step 5 [Deployment] Running SAM deploy..."
docker run -w /webauthnkit/backend --volume=$STARTER_KIT_DIR:/webauthnkit \
--volume=${HOME}/.aws:/home/developer/.aws:ro starterkit:dev \
-/home/developer/.local/bin/sam deploy \
+/usr/local/bin/sam deploy \
--s3-bucket $S3_BUCKET_NAME \
--stack-name $CF_STACK_NAME \
--profile $AWS_CLI_PROFILE \