diff --git a/containers/biometric-authenticator/.gitignore b/containers/biometric-authenticator/.gitignore
new file mode 100644
index 0000000..693002a
--- /dev/null
+++ b/containers/biometric-authenticator/.gitignore
@@ -0,0 +1,40 @@
+#Maven
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+release.properties
+.flattened-pom.xml
+
+# Eclipse
+.project
+.classpath
+.settings/
+bin/
+
+# IntelliJ
+.idea
+*.ipr
+*.iml
+*.iws
+
+# NetBeans
+nb-configuration.xml
+
+# Visual Studio Code
+.vscode
+.factorypath
+
+# OSX
+.DS_Store
+
+# Vim
+*.swp
+*.swo
+
+# patch
+*.orig
+*.rej
+
+# Local environment
+.env
diff --git a/containers/biometric-authenticator/.mvn/wrapper/.gitignore b/containers/biometric-authenticator/.mvn/wrapper/.gitignore
new file mode 100644
index 0000000..e72f5e8
--- /dev/null
+++ b/containers/biometric-authenticator/.mvn/wrapper/.gitignore
@@ -0,0 +1 @@
+maven-wrapper.jar
diff --git a/containers/biometric-authenticator/.mvn/wrapper/MavenWrapperDownloader.java b/containers/biometric-authenticator/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 0000000..1708393
--- /dev/null
+++ b/containers/biometric-authenticator/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader
+{
+ private static final String WRAPPER_VERSION = "3.1.1";
+
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL =
+ "https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/" + WRAPPER_VERSION
+ + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to use instead of the
+ * default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH = ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main( String args[] )
+ {
+ System.out.println( "- Downloader started" );
+ File baseDirectory = new File( args[0] );
+ System.out.println( "- Using base directory: " + baseDirectory.getAbsolutePath() );
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File( baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH );
+ String url = DEFAULT_DOWNLOAD_URL;
+ if ( mavenWrapperPropertyFile.exists() )
+ {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try
+ {
+ mavenWrapperPropertyFileInputStream = new FileInputStream( mavenWrapperPropertyFile );
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load( mavenWrapperPropertyFileInputStream );
+ url = mavenWrapperProperties.getProperty( PROPERTY_NAME_WRAPPER_URL, url );
+ }
+ catch ( IOException e )
+ {
+ System.out.println( "- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'" );
+ }
+ finally
+ {
+ try
+ {
+ if ( mavenWrapperPropertyFileInputStream != null )
+ {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ }
+ catch ( IOException e )
+ {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println( "- Downloading from: " + url );
+
+ File outputFile = new File( baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH );
+ if ( !outputFile.getParentFile().exists() )
+ {
+ if ( !outputFile.getParentFile().mkdirs() )
+ {
+ System.out.println( "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath()
+ + "'" );
+ }
+ }
+ System.out.println( "- Downloading to: " + outputFile.getAbsolutePath() );
+ try
+ {
+ downloadFileFromURL( url, outputFile );
+ System.out.println( "Done" );
+ System.exit( 0 );
+ }
+ catch ( Throwable e )
+ {
+ System.out.println( "- Error downloading" );
+ e.printStackTrace();
+ System.exit( 1 );
+ }
+ }
+
+ private static void downloadFileFromURL( String urlString, File destination )
+ throws Exception
+ {
+ if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null )
+ {
+ String username = System.getenv( "MVNW_USERNAME" );
+ char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray();
+ Authenticator.setDefault( new Authenticator()
+ {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication()
+ {
+ return new PasswordAuthentication( username, password );
+ }
+ } );
+ }
+ URL website = new URL( urlString );
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel( website.openStream() );
+ FileOutputStream fos = new FileOutputStream( destination );
+ fos.getChannel().transferFrom( rbc, 0, Long.MAX_VALUE );
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/containers/biometric-authenticator/.mvn/wrapper/maven-wrapper.properties b/containers/biometric-authenticator/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..61a2ef1
--- /dev/null
+++ b/containers/biometric-authenticator/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar
diff --git a/containers/biometric-authenticator/README.md b/containers/biometric-authenticator/README.md
new file mode 100644
index 0000000..ea94d69
--- /dev/null
+++ b/containers/biometric-authenticator/README.md
@@ -0,0 +1,9 @@
+# Auth
+
+A example template of a conversational service (chatbot) that requests identity document presentation, and then perform a remote biometric face verification with liveness detection, by comparing the credential embed subject photo with handset user face.
+
+## How it works
+
+- A User scans the QR code of the service. Service will request presentation of an Identity credential.
+- Service will receive the credential, extract embed photo
+- User will
\ No newline at end of file
diff --git a/containers/biometric-authenticator/docker/.gitignore b/containers/biometric-authenticator/docker/.gitignore
new file mode 100644
index 0000000..45c7ed6
--- /dev/null
+++ b/containers/biometric-authenticator/docker/.gitignore
@@ -0,0 +1 @@
+/afj/
diff --git a/containers/biometric-authenticator/docker/docker-compose.yml b/containers/biometric-authenticator/docker/docker-compose.yml
new file mode 100644
index 0000000..037fef5
--- /dev/null
+++ b/containers/biometric-authenticator/docker/docker-compose.yml
@@ -0,0 +1,78 @@
+version: '3.3'
+
+services:
+
+ artemis:
+ image: joylife/apache-artemis:2.20.0
+ ports:
+ - "8161:8161"
+ - "61616:61616"
+ - "5672:5672"
+ restart: unless-stopped
+ environment:
+ - ARTEMIS_USER=quarkus
+ - ARTEMIS_PASSWORD=Quar2060enbPi26
+# volumes:
+# - /tmp/artemis/artemis-instance:/var/lib/artemis-instance
+# - ./artemis/etc-override:/var/lib/artemis-instance/etc-override
+ networks:
+ - chatbot
+
+ service-agent:
+
+ image: gitlab.mobiera.com:4567/2060/2060-service-agent:dev
+
+ networks:
+ - chatbot
+ ports:
+ - 2600:3000
+ - 2601:3001
+ environment:
+ - AGENT_PUBLIC_DID=did:web:p2601.ovpndev.2060.io
+ - AGENT_ENDPOINT=wss://p2601.ovpndev.2060.io
+ - ANONCREDS_SERVICE_BASE_URL=https://p2601.ovpndev.2060.io
+ - AGENT_INVITATION_IMAGE_URL=https://p2603.ovpndev.2060.io/avatar.png
+ - AGENT_NAME=Auth Demo (mj)
+ - USE_CORS=true
+ - EVENTS_BASE_URL=https://p2603.ovpndev.2060.io
+ volumes:
+ - /tmp/afj:/root/.afj
+
+ postgres:
+ image: postgres:15.2
+ networks:
+ - chatbot
+ ports:
+ - 5432:5432
+ environment:
+ - POSTGRES_PASSWORD=2060demo
+ - POSTGRES_USER=gaia
+ - PGDATA=/var/lib/postgresql/data/pgdata
+ volumes:
+ - /tmp/postgresql:/var/lib/postgresql/data
+
+ datastore:
+ image: gitlab.mobiera.com:4567/2060/2060-data-store:dev
+ ports:
+ - "2604:2604"
+ restart: unless-stopped
+ environment:
+ - DEBUG=1
+ - QUARKUS_HTTP_PORT=2604
+ - IO_TWENTYSIXTY_DATASTORE_TMP_DIR=/tmp/data/tmp
+ - IO_TWENTYSIXTY_DATASTORE_TMP_LIFETIMEDAYS=5
+ - IO_TWENTYSIXTY_DATASTORE_REPO_LIFETIMEDAYS=43800
+ - IO_TWENTYSIXTY_DATASTORE_REPO_FS_DIR=/tmp/data/repo
+ - IO_TWENTYSIXTY_DATASTORE_MEDIA_MAXCHUNKS=128
+ volumes:
+ - /tmp/data:/tmp/data
+ networks:
+ - "chatbot"
+
+
+networks:
+ chatbot:
+ ipam:
+ driver: default
+ config:
+ - subnet: 172.28.0.0/27
diff --git a/containers/biometric-authenticator/mvnw b/containers/biometric-authenticator/mvnw
new file mode 100755
index 0000000..eaa3d30
--- /dev/null
+++ b/containers/biometric-authenticator/mvnw
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`\\unset -f command; \\command -v java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/containers/biometric-authenticator/mvnw.cmd b/containers/biometric-authenticator/mvnw.cmd
new file mode 100644
index 0000000..abb7c32
--- /dev/null
+++ b/containers/biometric-authenticator/mvnw.cmd
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/containers/biometric-authenticator/pom.xml b/containers/biometric-authenticator/pom.xml
new file mode 100644
index 0000000..efe20eb
--- /dev/null
+++ b/containers/biometric-authenticator/pom.xml
@@ -0,0 +1,228 @@
+
+
+ 4.0.0
+ io.2060.demos
+ biometric-authenticator
+ 1.0.0
+ Biometric Authenticator
+ How to authenticate a user by comparing her/his face with the photo embeded in her/his ID Verificable Credential
+ https://github.com/2060-io/2060-demos
+
+
+ The Apache License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+
+
+
+
+ Fabrice Rochette
+ f@2060.io
+ 2060 OÜ
+ https://2060.io
+
+
+
+ scm:git:git://github.com/2060-io/2060-demos.git
+ scm:git:ssh://github.com/2060-io/2060-demos.git
+ https://github.com/2060-io/2060-demos/tree/main
+
+
+
+ 3.10.1
+ 17
+ UTF-8
+ UTF-8
+ quarkus-bom
+ io.quarkus.platform
+ 3.6.4
+ true
+ 3.0.0-M7
+
+
+
+
+ ${quarkus.platform.group-id}
+ ${quarkus.platform.artifact-id}
+ ${quarkus.platform.version}
+ pom
+ import
+
+
+
+
+
+ io.twentysixty
+ 2060-service-agent-java-client
+ 2.0-quarkus3
+
+
+ com.sun.mail
+ javax.mail
+ 1.6.2
+
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-resteasy
+
+
+ org.jboss.resteasy
+ resteasy-multipart-provider
+
+
+ io.quarkus
+ quarkus-resteasy-jackson
+
+
+ io.quarkus
+ quarkus-smallrye-openapi
+
+
+ io.quarkus
+ quarkus-hibernate-orm
+
+
+ io.quarkus
+ quarkus-jdbc-postgresql
+
+
+ io.quarkus
+ quarkus-rest-client
+
+
+ io.quarkiverse.artemis
+ quarkus-artemis-core
+ 3.1.3
+
+
+ io.quarkiverse.artemis
+ quarkus-artemis-jms
+ 3.1.3
+
+
+ org.graalvm.sdk
+ graal-sdk
+
+
+ io.quarkus
+ quarkus-container-image-jib
+
+
+ commons-codec
+ commons-codec
+
+
+io.quarkus
+ quarkus-junit5
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+
+ ${quarkus.platform.group-id}
+ quarkus-maven-plugin
+ ${quarkus.platform.version}
+ true
+
+
+
+ build
+ generate-code
+ generate-code-tests
+
+
+
+
+
+ maven-compiler-plugin
+ ${compiler-plugin.version}
+
+
+ -parameters
+
+
+
+
+ maven-surefire-plugin
+ ${surefire-plugin.version}
+
+
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+ maven-failsafe-plugin
+ ${surefire-plugin.version}
+
+
+
+ integration-test
+ verify
+
+
+
+ ${project.build.directory}/${project.build.finalName}-runner
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+
+
+
+
+
+ native
+
+
+ native
+
+
+
+ false
+ native
+
+
+
+
+
+
+
+ gitlab-maven-2060-group
+ ${CI_API_V4_URL}/groups/80/-/packages/maven
+
+
+
+
+ sa-java-client-unauth
+ https://gitlab.mobiera.com/api/v4/projects/298/packages/maven
+
+
+
+
+
+
diff --git a/containers/biometric-authenticator/src/main/docker/Dockerfile.jvm b/containers/biometric-authenticator/src/main/docker/Dockerfile.jvm
new file mode 100644
index 0000000..6a6c9cc
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/docker/Dockerfile.jvm
@@ -0,0 +1,93 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./mvnw package
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/chatbot-demo-jvm .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/chatbot-demo-jvm
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/chatbot-demo-jvm
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+# in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+# used to calculate a default maximal heap memory based on a containers restriction.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+# of the container available memory as set here. The default is `50` which means 50%
+# of the available memory is used as an upper boundary. You can skip this mechanism by
+# setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+# is used to calculate a default initial heap memory based on the maximum heap memory.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+# is used as the initial heap size. You can skip this mechanism by setting this value
+# to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+# This is used to calculate the maximum value of the initial heap memory. If used in
+# a container without any memory constraints for the container then this option has
+# no effect. If there is a memory constraint then `-Xms` is limited to the value set
+# here. The default is 4096MB which means the calculated value of `-Xms` never will
+# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+# when things are happening. This option, if set to true, will set
+# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+# true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+# (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+# (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+# (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+# previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+# contain the necessary JRE command-line options to specify the required GC, which
+# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+# accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.14
+
+ENV LANGUAGE='en_US:en'
+
+
+# We make four distinct layers so if there are application changes the library layers can be re-used
+COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
+COPY --chown=185 target/quarkus-app/*.jar /deployments/
+COPY --chown=185 target/quarkus-app/app/ /deployments/app/
+COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
+
+EXPOSE 8080
+USER 185
+ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
+
diff --git a/containers/biometric-authenticator/src/main/docker/Dockerfile.legacy-jar b/containers/biometric-authenticator/src/main/docker/Dockerfile.legacy-jar
new file mode 100644
index 0000000..16f10a6
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/docker/Dockerfile.legacy-jar
@@ -0,0 +1,89 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
+#
+# Before building the container image run:
+#
+# ./mvnw package -Dquarkus.package.type=legacy-jar
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/chatbot-demo-legacy-jar .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/chatbot-demo-legacy-jar
+#
+# If you want to include the debug port into your docker image
+# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005
+#
+# Then run the container using :
+#
+# docker run -i --rm -p 8080:8080 quarkus/chatbot-demo-legacy-jar
+#
+# This image uses the `run-java.sh` script to run the application.
+# This scripts computes the command line to execute your Java application, and
+# includes memory/GC tuning.
+# You can configure the behavior using the following environment properties:
+# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
+# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
+# in JAVA_OPTS (example: "-Dsome.property=foo")
+# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
+# used to calculate a default maximal heap memory based on a containers restriction.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
+# of the container available memory as set here. The default is `50` which means 50%
+# of the available memory is used as an upper boundary. You can skip this mechanism by
+# setting this value to `0` in which case no `-Xmx` option is added.
+# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
+# is used to calculate a default initial heap memory based on the maximum heap memory.
+# If used in a container without any memory constraints for the container then this
+# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
+# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
+# is used as the initial heap size. You can skip this mechanism by setting this value
+# to `0` in which case no `-Xms` option is added (example: "25")
+# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
+# This is used to calculate the maximum value of the initial heap memory. If used in
+# a container without any memory constraints for the container then this option has
+# no effect. If there is a memory constraint then `-Xms` is limited to the value set
+# here. The default is 4096MB which means the calculated value of `-Xms` never will
+# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
+# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
+# when things are happening. This option, if set to true, will set
+# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
+# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
+# true").
+# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
+# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
+# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
+# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
+# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
+# (example: "20")
+# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
+# (example: "40")
+# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
+# (example: "4")
+# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
+# previous GC times. (example: "90")
+# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
+# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
+# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
+# contain the necessary JRE command-line options to specify the required GC, which
+# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
+# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
+# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
+# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
+# accessed directly. (example: "foo.example.com,bar.example.com")
+#
+###
+FROM registry.access.redhat.com/ubi8/openjdk-17:1.14
+
+ENV LANGUAGE='en_US:en'
+
+
+COPY target/lib/* /deployments/lib/
+COPY target/*-runner.jar /deployments/quarkus-run.jar
+
+EXPOSE 8080
+USER 185
+ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
+ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
diff --git a/containers/biometric-authenticator/src/main/docker/Dockerfile.native b/containers/biometric-authenticator/src/main/docker/Dockerfile.native
new file mode 100644
index 0000000..4301d2c
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/docker/Dockerfile.native
@@ -0,0 +1,27 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+#
+# Before building the container image run:
+#
+# ./mvnw package -Pnative
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native -t quarkus/chatbot-demo .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/chatbot-demo
+#
+###
+FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
+WORKDIR /work/
+RUN chown 1001 /work \
+ && chmod "g+rwX" /work \
+ && chown 1001:root /work
+COPY --chown=1001:root target/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
diff --git a/containers/biometric-authenticator/src/main/docker/Dockerfile.native-micro b/containers/biometric-authenticator/src/main/docker/Dockerfile.native-micro
new file mode 100644
index 0000000..5faebfc
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/docker/Dockerfile.native-micro
@@ -0,0 +1,30 @@
+####
+# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
+# It uses a micro base image, tuned for Quarkus native executables.
+# It reduces the size of the resulting container image.
+# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
+#
+# Before building the container image run:
+#
+# ./mvnw package -Pnative
+#
+# Then, build the image with:
+#
+# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/chatbot-demo .
+#
+# Then run the container using:
+#
+# docker run -i --rm -p 8080:8080 quarkus/chatbot-demo
+#
+###
+FROM quay.io/quarkus/quarkus-micro-image:2.0
+WORKDIR /work/
+RUN chown 1001 /work \
+ && chmod "g+rwX" /work \
+ && chown 1001:root /work
+COPY --chown=1001:root target/*-runner /work/application
+
+EXPOSE 8080
+USER 1001
+
+CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/ex/MediaAlreadyLinkedException.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/ex/MediaAlreadyLinkedException.java
new file mode 100644
index 0000000..a328883
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/ex/MediaAlreadyLinkedException.java
@@ -0,0 +1,7 @@
+package io.twentysixty.demos.auth.ex;
+
+public class MediaAlreadyLinkedException extends Exception {
+
+ private static final long serialVersionUID = -3472533317786392124L;
+
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/ex/NoMediaException.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/ex/NoMediaException.java
new file mode 100644
index 0000000..72c3d32
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/ex/NoMediaException.java
@@ -0,0 +1,7 @@
+package io.twentysixty.demos.auth.ex;
+
+public class NoMediaException extends Exception {
+
+ private static final long serialVersionUID = -3472533317786392124L;
+
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/ex/TokenException.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/ex/TokenException.java
new file mode 100644
index 0000000..0356cb8
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/ex/TokenException.java
@@ -0,0 +1,7 @@
+package io.twentysixty.demos.auth.ex;
+
+public class TokenException extends Exception {
+
+ private static final long serialVersionUID = -3472533317786392124L;
+
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/model/Session.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/model/Session.java
new file mode 100644
index 0000000..8b37601
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/model/Session.java
@@ -0,0 +1,136 @@
+package io.twentysixty.demos.auth.model;
+
+import java.io.Serializable;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.util.UUID;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.NamedQueries;
+import jakarta.persistence.NamedQuery;
+import jakarta.persistence.Table;
+
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
+
+
+
+/**
+ * The persistent class for the session database table.
+ *
+ */
+@Entity
+@Table(name="session")
+@DynamicUpdate
+@DynamicInsert
+@NamedQueries({
+ @NamedQuery(name="Session.findWithToken", query="SELECT s FROM Session s where s.token=:token"),
+
+})
+public class Session implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+
+ @Id
+ private UUID connectionId;
+
+
+
+ @Column(columnDefinition="text")
+ private String citizenId;
+ @Column(columnDefinition="text")
+ private String firstname;
+ @Column(columnDefinition="text")
+ private String lastname;
+
+ @Column(columnDefinition="date")
+ private LocalDate birthdate;
+ @Column(columnDefinition="text")
+ private String placeOfBirth;
+
+ private UUID photo;
+
+ private UUID token;
+
+ @Column(columnDefinition="timestamptz")
+ private Instant authTs;
+
+
+ public UUID getConnectionId() {
+ return connectionId;
+ }
+
+ public void setConnectionId(UUID connectionId) {
+ this.connectionId = connectionId;
+ }
+
+ public String getCitizenId() {
+ return citizenId;
+ }
+
+ public void setCitizenId(String citizenId) {
+ this.citizenId = citizenId;
+ }
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public void setFirstname(String firstname) {
+ this.firstname = firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+ public void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+ public LocalDate getBirthdate() {
+ return birthdate;
+ }
+
+ public void setBirthdate(LocalDate birthdate) {
+ this.birthdate = birthdate;
+ }
+
+ public String getPlaceOfBirth() {
+ return placeOfBirth;
+ }
+
+ public void setPlaceOfBirth(String placeOfBirth) {
+ this.placeOfBirth = placeOfBirth;
+ }
+
+ public UUID getPhoto() {
+ return photo;
+ }
+
+ public void setPhoto(UUID photo) {
+ this.photo = photo;
+ }
+
+ public UUID getToken() {
+ return token;
+ }
+
+ public void setToken(UUID token) {
+ this.token = token;
+ }
+
+ public Instant getAuthTs() {
+ return authTs;
+ }
+
+ public void setAuthTs(Instant authTs) {
+ this.authTs = authTs;
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/registry/jms/MoConsumer.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/registry/jms/MoConsumer.java
new file mode 100644
index 0000000..abbacfe
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/registry/jms/MoConsumer.java
@@ -0,0 +1,72 @@
+package io.twentysixty.demos.auth.registry.jms;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.event.Observes;
+import jakarta.inject.Inject;
+import jakarta.jms.ConnectionFactory;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.jboss.logging.Logger;
+
+import io.quarkus.runtime.ShutdownEvent;
+import io.quarkus.runtime.StartupEvent;
+import io.twentysixty.demos.auth.svc.Service;
+import io.twentysixty.sa.client.jms.AbstractConsumer;
+import io.twentysixty.sa.client.jms.ConsumerInterface;
+import io.twentysixty.sa.client.model.message.BaseMessage;
+
+@ApplicationScoped
+public class MoConsumer extends AbstractConsumer implements ConsumerInterface {
+
+ @Inject Service gaiaService;
+
+ @Inject
+ ConnectionFactory _connectionFactory;
+
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.jms.ex.delay")
+ Long _exDelay;
+
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.jms.mo.queue.name")
+ String _queueName;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.jms.mo.consumer.threads")
+ Integer _threads;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.debug")
+ Boolean _debug;
+
+ private static final Logger logger = Logger.getLogger(MoConsumer.class);
+
+
+ void onStart(@Observes StartupEvent ev) {
+
+ logger.info("onStart: SaConsumer queueName: " + _queueName);
+
+ this.setExDelay(_exDelay);
+ this.setDebug(_debug);
+ this.setQueueName(_queueName);
+ this.setThreads(_threads);
+ this.setConnectionFactory(_connectionFactory);
+ super._onStart();
+
+ }
+
+ void onStop(@Observes ShutdownEvent ev) {
+
+ logger.info("onStop: SaConsumer");
+
+ super._onStop();
+
+ }
+
+ @Override
+ public void receiveMessage(BaseMessage message) throws Exception {
+
+ gaiaService.userInput(message);
+
+ }
+
+
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/registry/jms/MoProducer.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/registry/jms/MoProducer.java
new file mode 100644
index 0000000..8f737e8
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/registry/jms/MoProducer.java
@@ -0,0 +1,71 @@
+package io.twentysixty.demos.auth.registry.jms;
+
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.event.Observes;
+import jakarta.inject.Inject;
+import jakarta.jms.ConnectionFactory;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.jboss.logging.Logger;
+
+import io.quarkus.runtime.ShutdownEvent;
+import io.quarkus.runtime.StartupEvent;
+import io.twentysixty.sa.client.jms.AbstractProducer;
+import io.twentysixty.sa.client.jms.ProducerInterface;
+import io.twentysixty.sa.client.model.message.BaseMessage;
+
+
+@ApplicationScoped
+public class MoProducer extends AbstractProducer implements ProducerInterface {
+
+ @Inject
+ ConnectionFactory _connectionFactory;
+
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.jms.ex.delay")
+ Long _exDelay;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.jms.mo.queue.name")
+ String _queueName;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.jms.mo.producer.threads")
+ Integer _threads;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.debug")
+ Boolean _debug;
+
+
+
+ private static final Logger logger = Logger.getLogger(MoProducer.class);
+
+
+ void onStart(@Observes StartupEvent ev) {
+ logger.info("onStart: SaProducer");
+
+ this.setExDelay(_exDelay);
+ this.setDebug(_debug);
+ this.setQueueName(_queueName);
+ this.setThreads(_threads);
+ this.setConnectionFactory(_connectionFactory);
+ this.setProducerCount(_threads);
+
+ }
+
+ void onStop(@Observes ShutdownEvent ev) {
+ logger.info("onStop: SaProducer");
+ }
+
+
+ @Override
+ public void sendMessage(BaseMessage message) throws Exception {
+ this.spool(message, 0);
+ }
+
+
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/registry/jms/MtConsumer.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/registry/jms/MtConsumer.java
new file mode 100644
index 0000000..454dbe1
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/registry/jms/MtConsumer.java
@@ -0,0 +1,79 @@
+package io.twentysixty.demos.auth.registry.jms;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.event.Observes;
+import jakarta.inject.Inject;
+import jakarta.jms.ConnectionFactory;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.jboss.logging.Logger;
+
+import io.quarkus.runtime.ShutdownEvent;
+import io.quarkus.runtime.StartupEvent;
+import io.twentysixty.sa.client.jms.AbstractConsumer;
+import io.twentysixty.sa.client.jms.ConsumerInterface;
+import io.twentysixty.sa.client.model.message.BaseMessage;
+import io.twentysixty.sa.res.c.MessageResource;
+
+@ApplicationScoped
+public class MtConsumer extends AbstractConsumer implements ConsumerInterface {
+
+ @RestClient
+ @Inject
+ MessageResource messageResource;
+
+
+ @Inject
+ ConnectionFactory _connectionFactory;
+
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.jms.ex.delay")
+ Long _exDelay;
+
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.jms.mt.queue.name")
+ String _queueName;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.jms.mt.consumer.threads")
+ Integer _threads;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.debug")
+ Boolean _debug;
+
+
+ private static final Logger logger = Logger.getLogger(MtConsumer.class);
+
+
+
+ void onStart(@Observes StartupEvent ev) {
+
+ logger.info("onStart: BeConsumer queueName: " + _queueName);
+
+ this.setExDelay(_exDelay);
+ this.setDebug(_debug);
+ this.setQueueName(_queueName);
+ this.setThreads(_threads);
+ this.setConnectionFactory(_connectionFactory);
+ super._onStart();
+
+ }
+
+ void onStop(@Observes ShutdownEvent ev) {
+
+ logger.info("onStop: BeConsumer");
+
+
+ super._onStop();
+
+ }
+
+ @Override
+ public void receiveMessage(BaseMessage message) throws Exception {
+
+ messageResource.sendMessage(message);
+
+ }
+
+
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/registry/jms/MtProducer.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/registry/jms/MtProducer.java
new file mode 100644
index 0000000..2bcfd0a
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/registry/jms/MtProducer.java
@@ -0,0 +1,73 @@
+package io.twentysixty.demos.auth.registry.jms;
+
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.event.Observes;
+import jakarta.inject.Inject;
+import jakarta.jms.ConnectionFactory;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.jboss.logging.Logger;
+
+import io.quarkus.runtime.ShutdownEvent;
+import io.quarkus.runtime.StartupEvent;
+import io.twentysixty.sa.client.jms.AbstractProducer;
+import io.twentysixty.sa.client.model.message.BaseMessage;
+import io.twentysixty.sa.client.util.JsonUtil;
+
+
+@ApplicationScoped
+public class MtProducer extends AbstractProducer {
+
+ @Inject
+ ConnectionFactory _connectionFactory;
+
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.jms.ex.delay")
+ Long _exDelay;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.jms.mt.queue.name")
+ String _queueName;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.jms.mt.producer.threads")
+ Integer _threads;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.debug")
+ Boolean _debug;
+
+
+
+ private static final Logger logger = Logger.getLogger(MtProducer.class);
+
+
+ void onStart(@Observes StartupEvent ev) {
+ logger.info("onStart: BeProducer");
+
+ this.setExDelay(_exDelay);
+ this.setDebug(_debug);
+ this.setQueueName(_queueName);
+ this.setThreads(_threads);
+ this.setConnectionFactory(_connectionFactory);
+
+ this.setProducerCount(_threads);
+
+ }
+
+ void onStop(@Observes ShutdownEvent ev) {
+
+ logger.info("onStop: BeProducer");
+ }
+
+
+ @Override
+ public void sendMessage(BaseMessage message) throws Exception {
+ if(_debug) {
+ logger.info("sendMessage: " + JsonUtil.serialize(message, false));
+ }
+ this.spool(message, 0);
+ }
+
+
+
+
+}
\ No newline at end of file
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/c/MediaResource.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/c/MediaResource.java
new file mode 100644
index 0000000..003ffe5
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/c/MediaResource.java
@@ -0,0 +1,51 @@
+package io.twentysixty.demos.auth.res.c;
+
+import java.util.UUID;
+
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.MediaType;
+
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;
+
+
+@RegisterRestClient
+@Path("")
+public interface MediaResource {
+
+
+ @GET
+ @Path("/r/{uuid}")
+ public byte[] render(@PathParam(value = "uuid") UUID uuid);
+
+
+ @PUT
+ @Path("/c/{uuid}/{noc}")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void createOrUpdate(@PathParam(value = "uuid") UUID uuid,
+ @PathParam(value = "noc") Integer numberOfChunks,
+ @QueryParam(value = "token") String token);
+
+
+ @PUT
+ @Path("/u/{uuid}/{c}")
+ @Consumes(MediaType.MULTIPART_FORM_DATA)
+ public void uploadChunk( @PathParam(value = "uuid") UUID uuid,
+ @PathParam(value = "c") Integer partNumber,
+ @QueryParam(value = "token") String token,
+ @MultipartForm Resource data);
+
+ @DELETE
+ @Path("/d/{uuid}")
+ public void delete( @PathParam(value = "uuid") UUID uuid,
+ @QueryParam(value = "token") String token);
+
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/c/Resource.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/c/Resource.java
new file mode 100644
index 0000000..fc9427c
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/c/Resource.java
@@ -0,0 +1,15 @@
+package io.twentysixty.demos.auth.res.c;
+
+import java.io.InputStream;
+
+import jakarta.ws.rs.FormParam;
+import jakarta.ws.rs.core.MediaType;
+
+import org.jboss.resteasy.annotations.providers.multipart.PartType;
+
+public class Resource {
+
+ @FormParam("chunk")
+ @PartType(MediaType.APPLICATION_OCTET_STREAM)
+ public InputStream chunk;
+}
\ No newline at end of file
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/s/ConnectionEventResource.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/s/ConnectionEventResource.java
new file mode 100644
index 0000000..81dc153
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/s/ConnectionEventResource.java
@@ -0,0 +1,58 @@
+package io.twentysixty.demos.auth.res.s;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.jboss.logging.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import io.twentysixty.demos.auth.svc.Service;
+import io.twentysixty.sa.client.model.event.ConnectionStateUpdated;
+import io.twentysixty.sa.client.model.event.DidExchangeState;
+import io.twentysixty.sa.client.res.s.ConnectionEventInterface;
+import io.twentysixty.sa.client.util.JsonUtil;
+
+@Path("")
+public class ConnectionEventResource implements ConnectionEventInterface {
+
+
+ private static Logger logger = Logger.getLogger(ConnectionEventResource.class);
+
+ @Inject Service service;
+ @ConfigProperty(name = "io.twentysixty.demos.auth.debug")
+ Boolean debug;
+
+
+ @Override
+ @POST
+ @Path("/connection-state-updated")
+ @Produces("application/json")
+ public Response connectionStateUpdated(ConnectionStateUpdated event) {
+ if (debug) {
+ try {
+ logger.info("connectionStateUpdated: " + JsonUtil.serialize(event, false));
+ } catch (JsonProcessingException e) {
+ logger.error("", e);
+ }
+ }
+ if (event.getState().equals(DidExchangeState.COMPLETED)) {
+ try {
+ service.newConnection(event.getConnectionId());
+ } catch (Exception e) {
+
+ logger.error("", e);
+ return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+ }
+ }
+
+ return Response.status(Status.OK).build();
+
+ }
+
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/s/MessageEventResource.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/s/MessageEventResource.java
new file mode 100644
index 0000000..2102211
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/s/MessageEventResource.java
@@ -0,0 +1,128 @@
+package io.twentysixty.demos.auth.res.s;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.jboss.logging.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import io.twentysixty.demos.auth.registry.jms.MoProducer;
+import io.twentysixty.demos.auth.registry.jms.MtProducer;
+import io.twentysixty.sa.client.model.event.MessageReceived;
+import io.twentysixty.sa.client.model.event.MessageState;
+import io.twentysixty.sa.client.model.event.MessageStateUpdated;
+import io.twentysixty.sa.client.model.message.MessageReceiptOptions;
+import io.twentysixty.sa.client.model.message.ReceiptsMessage;
+import io.twentysixty.sa.client.res.s.MessageEventInterface;
+import io.twentysixty.sa.client.util.JsonUtil;
+
+
+
+
+@Path("")
+public class MessageEventResource implements MessageEventInterface {
+
+ private static Logger logger = Logger.getLogger(MessageEventResource.class);
+
+
+
+ @Inject MoProducer moProducer;
+ @Inject MtProducer mtProducer;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.debug")
+ Boolean debug;
+
+ @Override
+ @POST
+ @Path("/message-received")
+ @Produces("application/json")
+ public Response messageReceived(MessageReceived event) {
+
+ if (debug) {
+ try {
+ logger.info("messageReceived: " + JsonUtil.serialize(event, false));
+ } catch (JsonProcessingException e) {
+ logger.error("", e);
+ }
+ }
+
+
+ List receipts = new ArrayList();
+
+ /*MessageReceiptOptions received = new MessageReceiptOptions();
+ received.setMessageId(event.getMessage().getId());
+ received.setTimestamp(Instant.now());
+ received.setState(MessageState.RECEIVED);
+ receipts.add(received);
+ */
+
+ MessageReceiptOptions viewed = new MessageReceiptOptions();
+ viewed.setMessageId(event.getMessage().getId());
+ viewed.setTimestamp(Instant.now());
+ viewed.setState(MessageState.VIEWED);
+ receipts.add(viewed);
+
+ ReceiptsMessage r = new ReceiptsMessage();
+ r.setConnectionId(event.getMessage().getConnectionId());
+ r.setReceipts(receipts);
+
+ try {
+ mtProducer.sendMessage(r);
+
+
+ //messageResource.sendMessage(r);
+ } catch (Exception e) {
+ logger.error("", e);
+ }
+
+
+ if (debug) {
+ try {
+ logger.info("messageReceived: sent receipts:" + JsonUtil.serialize(r, false));
+ } catch (JsonProcessingException e) {
+ logger.error("", e);
+ }
+ }
+
+ try {
+ moProducer.sendMessage(event.getMessage());
+ } catch (Exception e) {
+ logger.error("", e);
+ return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+ }
+
+ return Response.status(Status.OK).build();
+
+
+
+ }
+
+ @Override
+ @POST
+ @Path("/message-state-updated")
+ @Produces("application/json")
+ public Response messageStateUpdated(MessageStateUpdated event) {
+
+ if (debug) {
+ try {
+ logger.info("messageStateUpdated: " + JsonUtil.serialize(event, false));
+ } catch (JsonProcessingException e) {
+ logger.error("", e);
+ }
+ }
+ return Response.status(Status.OK).build();
+ }
+
+
+
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/s/VisionResource.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/s/VisionResource.java
new file mode 100644
index 0000000..76ec150
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/res/s/VisionResource.java
@@ -0,0 +1,140 @@
+package io.twentysixty.demos.auth.res.s;
+
+import java.util.List;
+import java.util.UUID;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.openapi.annotations.Operation;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
+import org.jboss.logging.Logger;
+
+import io.twentysixty.demos.auth.ex.TokenException;
+import io.twentysixty.demos.auth.svc.VisionService;
+
+@Path("")
+public class VisionResource {
+
+
+ private static Logger logger = Logger.getLogger(VisionResource.class);
+
+ @Inject VisionService service;
+ @ConfigProperty(name = "io.twentysixty.demos.auth.debug")
+ Boolean debug;
+
+
+
+
+
+
+ @GET
+ @Path("/list/{token}")
+ @Produces("application/json")
+ @Operation(summary = "List ids (UUID) of medias",
+ description = "List of mediaIds (UUID) of type {type} linked to identity represented by token {token}")
+ @APIResponses({
+ @APIResponse(responseCode = "400", description = "Check arguments or expired token"),
+ @APIResponse(responseCode = "403", description = "Permission Denied."),
+ @APIResponse(responseCode = "500", description = "Server error, please retry."),
+
+ @APIResponse(responseCode = "200", description = "OK") }
+ )
+ public Response listMedias(@PathParam(value = "token") UUID token) {
+
+ if (token == null) {
+ return Response.status(Status.BAD_REQUEST).build();
+ }
+
+ try {
+ List medias = service.listMedias(token);
+ return Response.status(Status.OK).entity(medias).build();
+ } catch (TokenException e) {
+ logger.error("", e);
+ return Response.status(Status.BAD_REQUEST).build();
+ } catch (Exception e) {
+ logger.error("", e);
+ return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+ }
+
+
+
+ }
+
+
+ @PUT
+ @Path("/success/{token}")
+ @Operation(summary = "Notify a successful verification or capture",
+ description = "Notify a successful verification or capture for identity represented by token {token}.")
+ @APIResponses({
+ @APIResponse(responseCode = "400", description = "Check arguments or expired token."),
+ @APIResponse(responseCode = "403", description = "Permission Denied."),
+ @APIResponse(responseCode = "500", description = "Server error, please retry."),
+
+ @APIResponse(responseCode = "200", description = "OK") }
+ )
+ public Response success(@PathParam(value = "token") UUID token) {
+
+ if (token == null) {
+ return Response.status(Status.BAD_REQUEST).build();
+ }
+
+ try {
+ service.success(token);
+
+ return Response.status(Status.OK).build();
+ } catch (TokenException e) {
+ logger.error("", e);
+ return Response.status(Status.BAD_REQUEST).build();
+ } catch (Exception e) {
+ logger.error("", e);
+ return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+ }
+
+
+ }
+
+ @PUT
+ @Path("/failure/{token}")
+ @Operation(summary = "Notify a failed verification or capture",
+ description = "Notify a failed verification or capture for Identity represented by token {token}.")
+ @APIResponses({
+ @APIResponse(responseCode = "400", description = "Check arguments or expired token."),
+ @APIResponse(responseCode = "403", description = "Permission Denied."),
+ @APIResponse(responseCode = "500", description = "Server error, please retry."),
+
+ @APIResponse(responseCode = "200", description = "OK") }
+ )
+ public Response failure(@PathParam(value = "token") UUID token) {
+
+ if (token == null ) {
+ return Response.status(Status.BAD_REQUEST).build();
+ }
+
+ try {
+ service.failure(token);
+
+ return Response.status(Status.OK).build();
+ } catch (TokenException e) {
+ logger.error("", e);
+ return Response.status(Status.BAD_REQUEST).build();
+ } catch (Exception e) {
+ logger.error("", e);
+ return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+ }
+
+
+ }
+
+
+}
+
+
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/svc/Service.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/svc/Service.java
new file mode 100644
index 0000000..2087f42
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/svc/Service.java
@@ -0,0 +1,504 @@
+package io.twentysixty.demos.auth.svc;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.ResourceBundle;
+import java.util.UUID;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.persistence.EntityManager;
+import jakarta.transaction.Transactional;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.graalvm.collections.Pair;
+import org.jboss.logging.Logger;
+import org.jgroups.util.Base64;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import io.twentysixty.demos.auth.model.Session;
+import io.twentysixty.demos.auth.registry.jms.MtProducer;
+import io.twentysixty.demos.auth.res.c.MediaResource;
+import io.twentysixty.demos.auth.res.c.Resource;
+import io.twentysixty.sa.client.model.credential.CredentialType;
+import io.twentysixty.sa.client.model.message.BaseMessage;
+import io.twentysixty.sa.client.model.message.Claim;
+import io.twentysixty.sa.client.model.message.ContextualMenuItem;
+import io.twentysixty.sa.client.model.message.ContextualMenuSelect;
+import io.twentysixty.sa.client.model.message.ContextualMenuUpdate;
+import io.twentysixty.sa.client.model.message.IdentityProofRequestMessage;
+import io.twentysixty.sa.client.model.message.IdentityProofSubmitMessage;
+import io.twentysixty.sa.client.model.message.InvitationMessage;
+import io.twentysixty.sa.client.model.message.MediaItem;
+import io.twentysixty.sa.client.model.message.MediaMessage;
+import io.twentysixty.sa.client.model.message.MenuSelectMessage;
+import io.twentysixty.sa.client.model.message.RequestedProofItem;
+import io.twentysixty.sa.client.model.message.SubmitProofItem;
+import io.twentysixty.sa.client.model.message.TextMessage;
+import io.twentysixty.sa.client.util.JsonUtil;
+import io.twentysixty.sa.res.c.CredentialTypeResource;
+
+
+
+@ApplicationScoped
+public class Service {
+
+ private static Logger logger = Logger.getLogger(Service.class);
+
+ @Inject EntityManager em;
+
+ @RestClient
+ @Inject MediaResource mediaResource;
+
+
+ @Inject MtProducer mtProducer;
+
+ @RestClient
+ @Inject CredentialTypeResource credentialTypeResource;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.debug")
+ Boolean debug;
+
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.credential_issuer")
+ String credentialIssuer;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.credential_issuer.avatar")
+ String invitationImageUrl;
+
+
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.id_credential_def")
+ String credDef;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.messages.welcome")
+ String WELCOME;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.messages.welcome2")
+ Optional WELCOME2;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.messages.welcome3")
+ Optional WELCOME3;
+
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.messages.nocred")
+ String NO_CRED_MSG;
+
+
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.language")
+ Optional language;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.vision.face.verification.url")
+ String faceVerificationUrl;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.vision.redirdomain")
+ Optional redirDomain;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.vision.redirdomain.q")
+ Optional qRedirDomain;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.vision.redirdomain.d")
+ Optional dRedirDomain;
+
+ private static String CMD_ROOT_MENU_AUTHENTICATE = "/auth";
+ private static String CMD_ROOT_MENU_NO_CRED = "/nocred";
+ private static String CMD_ROOT_MENU_OPTION1 = "/option1";
+ private static String CMD_ROOT_MENU_LOGOUT = "/logout";
+
+
+
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.messages.root.menu.title")
+ String ROOT_MENU_TITLE;
+
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.messages.root.menu.option1")
+ String ROOT_MENU_OPTION1;
+
+ @ConfigProperty(name = "io.twentysixty.demos.auth.messages.option1")
+ String OPTION1_MSG;
+
+
+
+
+
+
+ //private static HashMap sessions = new HashMap();
+ private static CredentialType type = null;
+ private static Object lockObj = new Object();
+
+
+
+ public void newConnection(UUID connectionId) throws Exception {
+ UUID threadId = UUID.randomUUID();
+ mtProducer.sendMessage(TextMessage.build(connectionId,threadId , WELCOME));
+ if (WELCOME2.isPresent()) {
+ mtProducer.sendMessage(TextMessage.build(connectionId, threadId, WELCOME2.get()));
+ }
+ if (WELCOME3.isPresent()) {
+ mtProducer.sendMessage(TextMessage.build(connectionId, threadId, WELCOME3.get()));
+ }
+
+
+ mtProducer.sendMessage(this.getRootMenu(connectionId, null));
+
+ //entryPointCreate(connectionId, null, null);
+ }
+
+ private BaseMessage getIdentityCredentialRequest(UUID connectionId, UUID threadId) {
+ IdentityProofRequestMessage ip = new IdentityProofRequestMessage();
+ ip.setConnectionId(connectionId);
+ ip.setThreadId(threadId);
+
+ RequestedProofItem id = new RequestedProofItem();
+ id.setCredentialDefinitionId(credDef);
+ id.setType("verifiable-credential");
+ List attributes = new ArrayList();
+ attributes.add("firstname");
+ attributes.add("lastname");
+ attributes.add("photo");
+ id.setAttributes(attributes);
+
+ List rpi = new ArrayList();
+ rpi.add(id);
+
+ ip.setRequestedProofItems(rpi);
+
+
+ try {
+ logger.info("getCredentialRequest: claim: " + JsonUtil.serialize(ip, false));
+ } catch (Exception e) {
+
+ }
+ return ip;
+ }
+
+ private Session getSession(UUID connectionId) {
+ Session session = em.find(Session.class, connectionId);
+ if (session == null) {
+ session = new Session();
+ session.setConnectionId(connectionId);
+ em.persist(session);
+
+ }
+
+ return session;
+ }
+
+
+
+ Pair getImage(String image) {
+ String mimeType = null;
+ byte[] imageBytes = null;
+
+ String[] separated = image.split(";");
+ if (separated.length>1) {
+ String[] mimeTypeData = separated[0].split(":");
+ String[] imageData = separated[1].split(",");
+
+ if (mimeTypeData.length>1) {
+ mimeType = mimeTypeData[1];
+ }
+ if (imageData.length>1) {
+ String base64Image = imageData[1];
+ if (base64Image != null) {
+ try {
+ imageBytes = Base64.decode(base64Image);
+ } catch (IOException e) {
+ logger.error("", e);
+ }
+ }
+ }
+
+ }
+
+ if (mimeType == null) return null;
+ if (imageBytes == null) return null;
+
+ return Pair.create(mimeType, imageBytes);
+
+ }
+ ResourceBundle bundle = null;
+
+ private String getMessage(String messageName) {
+ String retval = messageName;
+ if (bundle == null) {
+ if (language.isPresent()) {
+ try {
+ bundle = ResourceBundle.getBundle("META-INF/resources/Messages", new Locale(language.get()));
+ } catch (Exception e) {
+ bundle = ResourceBundle.getBundle("META-INF/resources/Messages", new Locale("en"));
+ }
+ } else {
+ bundle = ResourceBundle.getBundle("META-INF/resources/Messages", new Locale("en"));
+ }
+
+ }
+ try {
+ retval = bundle.getString(messageName);
+ } catch (Exception e) {
+
+ }
+
+
+ return retval;
+ }
+
+ private String buildVisionUrl(String url) {
+
+ if(redirDomain.isPresent()) {
+ url = url + "&rd=" + redirDomain.get();
+ }
+ if(qRedirDomain.isPresent()) {
+ url = url + "&q=" + qRedirDomain.get();
+ }
+ if(dRedirDomain.isPresent()) {
+ url = url + "&d=" + dRedirDomain.get();
+ }
+ if (language.isPresent()) {
+ url = url + "&lang=" + language.get();
+ }
+
+ return url;
+ }
+ private BaseMessage generateFaceVerificationMediaMessage(UUID connectionId, UUID threadId, String token) {
+ String url = faceVerificationUrl.replaceFirst("TOKEN", token);
+ url = this.buildVisionUrl(url);
+
+ MediaItem mi = new MediaItem();
+ mi.setMimeType("text/html");
+ mi.setUri(url);
+ mi.setTitle(getMessage("FACE_VERIFICATION_HEADER"));
+ mi.setDescription(getMessage("FACE_VERIFICATION_DESC"));
+ mi.setOpeningMode("normal");
+ List mis = new ArrayList();
+ mis.add(mi);
+ MediaMessage mm = new MediaMessage();
+ mm.setConnectionId(connectionId);
+ mm.setThreadId(threadId);
+ mm.setDescription(getMessage("FACE_VERIFICATION_DESC"));
+ mm.setItems(mis);
+ return mm;
+ }
+
+
+ @Transactional
+ public void userInput(BaseMessage message) throws Exception {
+
+ Session session = this.getSession(message.getConnectionId());
+
+
+ String content = null;
+
+ MediaMessage mm = null;
+
+ if (message instanceof TextMessage) {
+
+ TextMessage textMessage = (TextMessage) message;
+ content = textMessage.getContent();
+
+ } else if ((message instanceof ContextualMenuSelect) ) {
+
+ ContextualMenuSelect menuSelect = (ContextualMenuSelect) message;
+ content = menuSelect.getSelectionId();
+
+ } else if ((message instanceof MenuSelectMessage)) {
+
+ MenuSelectMessage menuSelect = (MenuSelectMessage) message;
+ content = menuSelect.getMenuItems().iterator().next().getId();
+ } else if ((message instanceof MediaMessage)) {
+ mm = (MediaMessage) message;
+ content = "media";
+ } else if ((message instanceof IdentityProofSubmitMessage)) {
+ if (session.getAuthTs() == null) {
+ try {
+ logger.info("userInput: claim: " + JsonUtil.serialize(message, false));
+ } catch (JsonProcessingException e) {
+
+ }
+ boolean sentVerifLink = false;
+ IdentityProofSubmitMessage ipm = (IdentityProofSubmitMessage) message;
+ if (ipm.getSubmittedProofItems().size()>0) {
+
+ SubmitProofItem sp = ipm.getSubmittedProofItems().iterator().next();
+ if (sp.getClaims().size()>0) {
+
+ UUID identityId = null;
+ String firstname = null;
+ String lastname = null;
+ String photo = null;
+
+ for (Claim c: sp.getClaims()) {
+ if (c.getName().equals("id")) {
+ identityId = UUID.fromString(c.getValue());
+ } else if (c.getName().equals("firstname")) {
+ firstname = c.getValue();
+ } else if (c.getName().equals("lastname")) {
+ lastname = c.getValue();
+ } else if (c.getName().equals("photo")) {
+ photo = c.getValue();
+ logger.info("userInput: photo: " + photo);
+ }
+ }
+ session.setFirstname(firstname);
+ session.setLastname(lastname);
+
+ if (photo != null) {
+ Pair imageData = getImage(photo);
+ if (imageData != null) {
+ UUID mediaUUID = UUID.randomUUID();
+ mediaResource.createOrUpdate(mediaUUID, 1, mediaUUID.toString());
+
+
+ File file = new File(System.getProperty("java.io.tmpdir") + "/" + mediaUUID);
+
+ FileOutputStream fos = new FileOutputStream(file);
+ fos.write(imageData.getRight());
+ fos.flush();
+ fos.close();
+
+ Resource r = new Resource();
+ r.chunk = new FileInputStream(file);
+ mediaResource.uploadChunk(mediaUUID, 0, mediaUUID.toString(), r);
+
+ file.delete();
+ session.setPhoto(mediaUUID);
+ session.setToken(UUID.randomUUID());
+ em.merge(session);
+
+ mtProducer.sendMessage(generateFaceVerificationMediaMessage(message.getConnectionId(), message.getThreadId(), session.getToken().toString()));
+
+ sentVerifLink = true;
+ }
+ }
+
+
+ }
+ }
+ if (!sentVerifLink) {
+ mtProducer.sendMessage(TextMessage.build(message.getConnectionId(), message.getThreadId() , this.getMessage("CREDENTIAL_ERROR")));
+
+ }
+ }
+
+ }
+ if (content != null) {
+ if (content.equals(CMD_ROOT_MENU_AUTHENTICATE.toString())) {
+ mtProducer.sendMessage(this.getIdentityCredentialRequest(message.getConnectionId(), message.getThreadId()));
+ } else if (content.equals(CMD_ROOT_MENU_OPTION1.toString())) {
+ mtProducer.sendMessage(TextMessage.build(message.getConnectionId(), message.getThreadId() , OPTION1_MSG));
+
+ } else if (content.equals(CMD_ROOT_MENU_NO_CRED.toString())) {
+
+ mtProducer.sendMessage(TextMessage.build(message.getConnectionId(), message.getThreadId() , NO_CRED_MSG));
+
+
+ InvitationMessage invitation = new InvitationMessage();
+ invitation.setConnectionId(message.getConnectionId());
+ invitation.setThreadId(message.getThreadId());
+ invitation.setImageUrl(invitationImageUrl);
+ invitation.setDid(credentialIssuer);
+
+ mtProducer.sendMessage(invitation);
+
+ } else if (content.equals(CMD_ROOT_MENU_LOGOUT.toString())) {
+ if (session != null) {
+ session.setAuthTs(null);
+ session = em.merge(session);
+ }
+ mtProducer.sendMessage(TextMessage.build(message.getConnectionId(), message.getThreadId() , this.getMessage("UNAUTHENTICATED")));
+
+ } else {
+ mtProducer.sendMessage(TextMessage.build(message.getConnectionId(), message.getThreadId() , this.getMessage("ERROR")));
+ }
+ }
+ mtProducer.sendMessage(this.getRootMenu(message.getConnectionId(), session));
+ }
+
+ @Transactional
+ public void notifySuccess(UUID connectionId) {
+ Session session = em.find(Session.class, connectionId);
+ if (session != null) {
+ try {
+ session.setAuthTs(Instant.now());
+ session = em.merge(session);
+ mtProducer.sendMessage(TextMessage.build(connectionId, null , this.getMessage("AUTHENTICATION_SUCCESS")));
+ } catch (Exception e) {
+ logger.error("", e);
+ }
+ }
+ try {
+ mtProducer.sendMessage(this.getRootMenu(connectionId, session));
+ } catch (Exception e) {
+ logger.error("", e);
+ }
+ }
+
+ public void notifyFailure(UUID connectionId) {
+ Session session = em.find(Session.class, connectionId);
+ if (session != null) {
+ try {
+ mtProducer.sendMessage(TextMessage.build(connectionId, null , this.getMessage("AUTHENTICATION_ERROR")));
+
+ mtProducer.sendMessage(generateFaceVerificationMediaMessage(connectionId, null, session.getToken().toString()));
+ } catch (Exception e) {
+ logger.error("", e);
+ }
+ }
+ }
+
+
+
+ public BaseMessage getRootMenu(UUID connectionId, Session session) {
+
+ ContextualMenuUpdate menu = new ContextualMenuUpdate();
+ menu.setTitle(ROOT_MENU_TITLE);
+
+
+ List options = new ArrayList();
+
+
+ if ((session == null) || (session.getAuthTs() == null) ){
+ menu.setDescription(getMessage("ROOT_MENU_DEFAULT_DESCRIPTION"));
+ options.add(ContextualMenuItem.build(CMD_ROOT_MENU_AUTHENTICATE, getMessage("ROOT_MENU_AUTHENTICATE"), null));
+ options.add(ContextualMenuItem.build(CMD_ROOT_MENU_NO_CRED, getMessage("ROOT_MENU_NO_CRED"), null));
+
+ } else {
+ menu.setDescription(getMessage("ROOT_MENU_AUTHENTICATED_DESCRIPTION").replaceAll("NAME", session.getFirstname() + " " + session.getLastname()));
+
+ options.add(ContextualMenuItem.build(CMD_ROOT_MENU_OPTION1, ROOT_MENU_OPTION1, null));
+ options.add(ContextualMenuItem.build(CMD_ROOT_MENU_LOGOUT, this.getMessage("ROOT_MENU_LOGOUT"), null));
+
+ }
+
+
+
+ menu.setOptions(options);
+
+
+
+ if (debug) {
+ try {
+ logger.info("getRootMenu: " + JsonUtil.serialize(menu, false));
+ } catch (JsonProcessingException e) {
+ }
+ }
+ menu.setConnectionId(connectionId);
+ menu.setId(UUID.randomUUID());
+ menu.setTimestamp(Instant.now());
+
+ return menu;
+
+
+ }
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/svc/SessionData.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/svc/SessionData.java
new file mode 100644
index 0000000..1c05c23
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/svc/SessionData.java
@@ -0,0 +1,60 @@
+package io.twentysixty.demos.auth.svc;
+
+import java.time.LocalDate;
+import java.util.UUID;
+
+
+public class SessionData {
+
+
+ private String firstname;
+ private String lastname;
+ private LocalDate birthdate;
+ private String placeOfBirth;
+
+ private UUID identityId;
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public void setFirstname(String firstname) {
+ this.firstname = firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+ public void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+ public LocalDate getBirthdate() {
+ return birthdate;
+ }
+
+ public void setBirthdate(LocalDate birthdate) {
+ this.birthdate = birthdate;
+ }
+
+
+
+ public String getPlaceOfBirth() {
+ return placeOfBirth;
+ }
+
+ public void setPlaceOfBirth(String placeOfBirth) {
+ this.placeOfBirth = placeOfBirth;
+ }
+
+ public UUID getIdentityId() {
+ return identityId;
+ }
+
+ public void setIdentityId(UUID identityId) {
+ this.identityId = identityId;
+ }
+
+
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/svc/VisionService.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/svc/VisionService.java
new file mode 100644
index 0000000..642f630
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/svc/VisionService.java
@@ -0,0 +1,83 @@
+package io.twentysixty.demos.auth.svc;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.Query;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+
+import io.twentysixty.demos.auth.ex.TokenException;
+import io.twentysixty.demos.auth.model.Session;
+
+
+@ApplicationScoped
+public class VisionService {
+
+
+ @Inject Service service;
+
+ @Inject EntityManager em;
+
+
+
+ private Session getSession(UUID token) throws TokenException {
+
+ Session session = null;
+
+ Query q = em.createNamedQuery("Session.findWithToken");
+ q.setParameter("token", token);
+
+ session = (Session) q.getResultList().stream().findFirst().orElse(null);
+ if (session == null) {
+ throw new TokenException();
+ }
+
+ return session;
+ }
+
+ public List listMedias(UUID token) throws Exception {
+
+
+ Session session = this.getSession(token);
+
+
+ List response = new ArrayList(1);
+ response.add(session.getPhoto());
+
+ return response;
+
+
+
+ }
+
+
+
+ public void success(UUID token) throws Exception {
+
+ Session session = this.getSession(token);
+
+ service.notifySuccess(session.getConnectionId());
+
+
+ }
+
+
+ public void failure(UUID token) throws Exception {
+
+ Session session = this.getSession(token);
+
+ service.notifyFailure(session.getConnectionId());
+
+
+
+
+ }
+
+
+
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/testing/Test.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/testing/Test.java
new file mode 100644
index 0000000..5c0b52d
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/testing/Test.java
@@ -0,0 +1,16 @@
+package io.twentysixty.demos.auth.testing;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+public class Test {
+ private static DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+
+ public static void main(String[] args) {
+ // TODO Auto-generated method stub
+
+ LocalDate birthDate = LocalDate.from(df.parse("2020-01-01"));
+ System.out.println(birthDate);
+ }
+
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/testing/TestRB.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/testing/TestRB.java
new file mode 100644
index 0000000..c21ff69
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/demos/auth/testing/TestRB.java
@@ -0,0 +1,22 @@
+package io.twentysixty.demos.auth.testing;
+
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+public class TestRB {
+
+ public static void main(String[] args) {
+
+
+ ResourceBundle bundle = ResourceBundle.getBundle("META-INF/resources/Messages", Locale.US);
+ System.out.println("Message in "+Locale.US +":"+bundle.getString("ROOT_MENU_TITLE"));
+
+
+ Locale es = new Locale("ES");
+ bundle = ResourceBundle.getBundle("META-INF/resources/Messages", es);
+ System.out.println("Message in "+es +":"+bundle.getString("ROOT_MENU_TITLE"));
+
+
+ }
+
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/sa/res/c/CredentialTypeResource.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/sa/res/c/CredentialTypeResource.java
new file mode 100644
index 0000000..0b87f20
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/sa/res/c/CredentialTypeResource.java
@@ -0,0 +1,12 @@
+package io.twentysixty.sa.res.c;
+
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+import io.twentysixty.sa.client.res.c.CredentialTypeInterface;
+
+
+@RegisterRestClient
+public interface CredentialTypeResource extends CredentialTypeInterface {
+
+
+}
diff --git a/containers/biometric-authenticator/src/main/java/io/twentysixty/sa/res/c/MessageResource.java b/containers/biometric-authenticator/src/main/java/io/twentysixty/sa/res/c/MessageResource.java
new file mode 100644
index 0000000..007d120
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/java/io/twentysixty/sa/res/c/MessageResource.java
@@ -0,0 +1,12 @@
+package io.twentysixty.sa.res.c;
+
+import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
+
+import io.twentysixty.sa.client.res.c.MessageInterface;
+
+
+@RegisterRestClient
+public interface MessageResource extends MessageInterface {
+
+
+}
diff --git a/containers/biometric-authenticator/src/main/resources/META-INF/resources/Messages_en.properties b/containers/biometric-authenticator/src/main/resources/META-INF/resources/Messages_en.properties
new file mode 100644
index 0000000..e8f4bca
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/resources/META-INF/resources/Messages_en.properties
@@ -0,0 +1,12 @@
+CREDENTIAL_ERROR=Credential is not compatible.
+AUTHENTICATION_SUCCESS=Authentication successful.
+AUTHENTICATION_ERROR=Authentication error.
+ROOT_MENU_AUTHENTICATE=Authenticate
+ROOT_MENU_NO_CRED=Get an Identity Card
+ROOT_MENU_LOGOUT=Logout
+ROOT_MENU_DEFAULT_DESCRIPTION=You are not authenticated
+ROOT_MENU_AUTHENTICATED_DESCRIPTION=Welcome NAME!
+ERROR=We couldn't understand your message. Please check the contextual menu for available options.
+FACE_VERIFICATION_HEADER=Face Verification
+FACE_VERIFICATION_DESC=Start Face Verification to Authenticate yourself
+UNAUTHENTICATED=You are now unauthenticated.
\ No newline at end of file
diff --git a/containers/biometric-authenticator/src/main/resources/META-INF/resources/Messages_es.properties b/containers/biometric-authenticator/src/main/resources/META-INF/resources/Messages_es.properties
new file mode 100644
index 0000000..a1be65c
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/resources/META-INF/resources/Messages_es.properties
@@ -0,0 +1,12 @@
+CREDENTIAL_ERROR=La Credencial no es compatible.
+AUTHENTICATION_SUCCESS=Ya estás autenticado.
+AUTHENTICATION_ERROR=Error de autenticación.
+ROOT_MENU_AUTHENTICATE=Autenticate
+ROOT_MENU_NO_CRED=Obtiene tu documento de Identidad
+ROOT_MENU_LOGOUT=Desconectarse
+ROOT_MENU_DEFAULT_DESCRIPTION=No estas autenticado
+ROOT_MENU_AUTHENTICATED_DESCRIPTION=¡Bienvenido NAME!
+ERROR=No hemos podido entender tu mensaje. Accede al menú contextual para ver las opciones disponibles.
+FACE_VERIFICATION_HEADER=Verificación biométrica
+FACE_VERIFICATION_DESC=Autentícate con tu rostro
+UNAUTHENTICATED=Estas desconectado.
\ No newline at end of file
diff --git a/containers/biometric-authenticator/src/main/resources/META-INF/resources/Messages_fr.properties b/containers/biometric-authenticator/src/main/resources/META-INF/resources/Messages_fr.properties
new file mode 100644
index 0000000..edf1b2c
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/resources/META-INF/resources/Messages_fr.properties
@@ -0,0 +1,12 @@
+CREDENTIAL_ERROR=Attestation incompatible.
+AUTHENTICATION_SUCCESS=Authentification réussie.
+AUTHENTICATION_ERROR=Erreur lors de l'authentification.
+ROOT_MENU_AUTHENTICATE=S'authentifier
+ROOT_MENU_NO_CRED=Obtenir une Attestation d'Identité
+ROOT_MENU_LOGOUT=Se Deconnecter
+ROOT_MENU_DEFAULT_DESCRIPTION=Vous n'êtes pas authentifié
+ROOT_MENU_AUTHENTICATED_DESCRIPTION=Bienvenue NAME!
+ERROR=Nous n'avons pas compris votre messages. Accedez au menu contextuel pour les options disponibles.
+FACE_VERIFICATION_HEADER=Vérification biométrique
+FACE_VERIFICATION_DESC=Authentifiez-vous avec votre visage
+UNAUTHENTICATED=Vous êtes déconnecté.
\ No newline at end of file
diff --git a/containers/biometric-authenticator/src/main/resources/META-INF/resources/avatar.old.png b/containers/biometric-authenticator/src/main/resources/META-INF/resources/avatar.old.png
new file mode 100644
index 0000000..2623fa1
Binary files /dev/null and b/containers/biometric-authenticator/src/main/resources/META-INF/resources/avatar.old.png differ
diff --git a/containers/biometric-authenticator/src/main/resources/META-INF/resources/avatar.old2.png b/containers/biometric-authenticator/src/main/resources/META-INF/resources/avatar.old2.png
new file mode 100644
index 0000000..8fef2d9
Binary files /dev/null and b/containers/biometric-authenticator/src/main/resources/META-INF/resources/avatar.old2.png differ
diff --git a/containers/biometric-authenticator/src/main/resources/META-INF/resources/avatar.png b/containers/biometric-authenticator/src/main/resources/META-INF/resources/avatar.png
new file mode 100644
index 0000000..1129a82
Binary files /dev/null and b/containers/biometric-authenticator/src/main/resources/META-INF/resources/avatar.png differ
diff --git a/containers/biometric-authenticator/src/main/resources/META-INF/resources/gaia.png b/containers/biometric-authenticator/src/main/resources/META-INF/resources/gaia.png
new file mode 100644
index 0000000..1129a82
Binary files /dev/null and b/containers/biometric-authenticator/src/main/resources/META-INF/resources/gaia.png differ
diff --git a/containers/biometric-authenticator/src/main/resources/application.properties b/containers/biometric-authenticator/src/main/resources/application.properties
new file mode 100644
index 0000000..0f56069
--- /dev/null
+++ b/containers/biometric-authenticator/src/main/resources/application.properties
@@ -0,0 +1,80 @@
+
+
+io.twentysixty.demos.auth.token.lifetimeseconds=900
+io.twentysixty.demos.auth.token.lifetimeseconds=900
+
+
+io.twentysixty.demos.auth.language=en
+io.twentysixty.demos.auth.messages.welcome=Welcome to the Auth demo service! By using this demo service you'll understand how to authenticate a user by requesting an identity credential, and by demonstrating that the mobile phone user is the same person that the one in the presented credential.
+io.twentysixty.demos.auth.messages.root.menu.title=Auth demo service
+io.twentysixty.demos.auth.messages.root.menu.option1=Request Support
+io.twentysixty.demos.auth.messages.option1=You selected option1 in contextual menu
+io.twentysixty.demos.auth.messages.nocred=If you do not have a GaiaID Identity Card (required to authenticate yourself), get one by connecting to below service.
+
+%dev.io.twentysixty.demos.auth.vision.redirdomain=p2604.ovpndev.2060.io
+%dev.io.twentysixty.demos.auth.vision.redirdomain.q=p2603.ovpndev.2060.io
+%dev.io.twentysixty.demos.auth.vision.redirdomain.d=p2604.ovpndev.2060.io
+
+io.twentysixty.demos.auth.vision.face.verification.url=https://vision-t.gaiaid.io/face/verification/?token=TOKEN
+
+
+io.twentysixty.demos.auth.credential_issuer=did:web:registry.demos.dev.2060.io
+io.twentysixty.demos.auth.credential_issuer.avatar=https://q.registry.demos.dev.2060.io/gaia.png
+io.twentysixty.demos.auth.id_credential_def=did:web:registry.demos.dev.2060.io?service=anoncreds&relativeRef=/credDef/7QxmjvXdW2Z4tg1cZ5aSwtvBukasoH5MGn5Dx73wpmeh
+
+
+io.twentysixty.demos.auth.jms.mo.consumer.threads=1
+io.twentysixty.demos.auth.jms.mo.producer.threads=1
+io.twentysixty.demos.auth.jms.mo.queue.name=gaia-mo
+io.twentysixty.demos.auth.jms.mt.consumer.threads=1
+io.twentysixty.demos.auth.jms.mt.producer.threads=1
+io.twentysixty.demos.auth.jms.mt.queue.name=gaia-mt
+io.twentysixty.demos.auth.jms.ex.delay=10000
+
+
+io.twentysixty.demos.auth.debug=true
+
+io.twentysixty.sa.res.c.CredentialTypeResource/mp-rest/url=http://sa:3000/
+io.twentysixty.sa.res.c.MessageResource/mp-rest/url=http://sa:3000/
+
+
+%dev.io.twentysixty.sa.res.c.CredentialTypeResource/mp-rest/url=http://localhost:2600/
+%dev.io.twentysixty.sa.res.c.MessageResource/mp-rest/url=http://localhost:2600/
+
+io.twentysixty.demos.auth.res.c.MediaResource/mp-rest/url=http://ds:2904/
+%dev.io.twentysixty.demos.auth.res.c.MediaResource/mp-rest/url=http://localhost:2604/
+
+
+quarkus.http.host=0.0.0.0
+quarkus.http.port=2903
+
+%dev.quarkus.http.port=2603
+
+
+
+quarkus.datasource.db-kind=postgresql
+quarkus.datasource.username=gaia
+quarkus.datasource.password=****
+quarkus.datasource.jdbc.url=jdbc:postgresql://localhost/gaia
+%dev.quarkus.datasource.jdbc.url=jdbc:postgresql://localhost/gaia
+quarkus.datasource.jdbc.max-size=8
+quarkus.datasource.jdbc.min-size=2
+
+quarkus.hibernate-orm.database.generation=update
+
+quarkus.artemis.url=tcp://127.0.0.1:61616
+quarkus.artemis.username=quarkus
+quarkus.artemis.password=****
+
+quarkus.swagger-ui.always-include=true
+
+
+quarkus.container-image.build=true
+quarkus.container-image.push=true
+quarkus.container-image.group=io2060
+quarkus.container-image.registry=registry.hub.docker.com
+quarkus.container-image.name=2060-demos-biometric-authenticator
+quarkus.jib.ports=8080
+
+
+
diff --git a/containers/citizen-registry/pom.xml b/containers/citizen-registry/pom.xml
index 6d15294..8da1393 100644
--- a/containers/citizen-registry/pom.xml
+++ b/containers/citizen-registry/pom.xml
@@ -7,7 +7,7 @@
1.0.0
Citizen Registry
A real DIDcomm citizen registry
- https://github.com/2060-io/didcomm-service-demos
+ https://github.com/2060-io/2060-demos
The Apache License, Version 2.0
@@ -23,9 +23,9 @@
- scm:git:git://github.com/2060-io/didcomm-service-demos.git
- scm:git:ssh://github.com/2060-io/didcomm-service-demos.git
- https://github.com/2060-io/didcomm-service-demos/tree/main
+ scm:git:git://github.com/2060-io/2060-demos.git
+ scm:git:ssh://github.com/2060-io/2060-demos.git
+ https://github.com/2060-io/2060-demos/tree/main
diff --git a/containers/citizen-registry/src/main/resources/application.properties b/containers/citizen-registry/src/main/resources/application.properties
index b41dc7f..a0fdb22 100644
--- a/containers/citizen-registry/src/main/resources/application.properties
+++ b/containers/citizen-registry/src/main/resources/application.properties
@@ -110,7 +110,6 @@ quarkus.container-image.push=true
quarkus.container-image.group=io2060
quarkus.container-image.registry=registry.hub.docker.com
quarkus.container-image.name=2060-demos-citizen-registry
-quarkus.container-image.tag=main
quarkus.jib.ports=8080
diff --git a/containers/hello-world/pom.xml b/containers/hello-world/pom.xml
index cbd5f49..51b0b58 100644
--- a/containers/hello-world/pom.xml
+++ b/containers/hello-world/pom.xml
@@ -7,7 +7,7 @@
1.0.0
hello World Demo Container
A simple DIDComm demo conversational service
- https://github.com/2060-io/didcomm-service-demos
+ https://github.com/2060-io/2060-demos
The Apache License, Version 2.0
@@ -23,9 +23,9 @@
- scm:git:git://github.com/2060-io/didcomm-service-demos.git
- scm:git:ssh://github.com/2060-io/didcomm-service-demos.git
- https://github.com/2060-io/didcomm-service-demos/tree/main
+ scm:git:git://github.com/2060-io/2060-demos.git
+ scm:git:ssh://github.com/2060-io/2060-demos.git
+ https://github.com/2060-io/2060-demos/tree/main
3.10.1
diff --git a/containers/hello-world/src/main/resources/application.properties b/containers/hello-world/src/main/resources/application.properties
index 31814ff..a126aad 100644
--- a/containers/hello-world/src/main/resources/application.properties
+++ b/containers/hello-world/src/main/resources/application.properties
@@ -14,5 +14,4 @@ quarkus.container-image.push=true
quarkus.container-image.group=io2060
quarkus.container-image.registry=registry.hub.docker.com
quarkus.container-image.name=2060-demos-hello-world
-quarkus.container-image.tag=main
quarkus.jib.ports=8080