From a6d34f6b41ae1c567cc13cc1984fb6853961cb27 Mon Sep 17 00:00:00 2001 From: mehmet-yoti <111424390+mehmet-yoti@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:35:22 +0300 Subject: [PATCH] Sdk 2265 examples (#354) * SDK-2265-examples - updated routes * SDK-2265 anchor updates * SDK-2265 removed profile attributes, updated namings, removed unnecessary comments --- examples/digitalidentity/.env.example | 14 + examples/digitalidentity/.gitignore | 16 + examples/digitalidentity/README.md | 24 + .../digitalidentity/app/Console/Kernel.php | 41 ++ .../app/Exceptions/Handler.php | 55 +++ .../Http/Controllers/IdentityController.php | 60 +++ .../Http/Controllers/ReceiptController.php | 124 +++++ examples/digitalidentity/app/Http/Kernel.php | 66 +++ .../app/Http/Middleware/Authenticate.php | 21 + .../Middleware/CheckForMaintenanceMode.php | 17 + .../app/Http/Middleware/EncryptCookies.php | 17 + .../Middleware/RedirectIfAuthenticated.php | 27 ++ .../app/Http/Middleware/TrimStrings.php | 18 + .../app/Http/Middleware/TrustProxies.php | 23 + .../app/Http/Middleware/VerifyCsrfToken.php | 17 + .../app/Providers/RouteServiceProvider.php | 80 ++++ .../YotiDigitalIdentityServiceProvider.php | 29 ++ .../app/Providers/YotiServiceProvider.php | 29 ++ examples/digitalidentity/artisan | 53 +++ examples/digitalidentity/bootstrap/app.php | 55 +++ .../bootstrap/cache/.gitignore | 2 + examples/digitalidentity/composer.json | 59 +++ examples/digitalidentity/config/app.php | 229 ++++++++++ examples/digitalidentity/config/auth.php | 117 +++++ .../digitalidentity/config/broadcasting.php | 59 +++ examples/digitalidentity/config/cache.php | 104 +++++ examples/digitalidentity/config/cors.php | 34 ++ examples/digitalidentity/config/database.php | 147 ++++++ .../digitalidentity/config/filesystems.php | 84 ++++ examples/digitalidentity/config/hashing.php | 52 +++ examples/digitalidentity/config/logging.php | 104 +++++ examples/digitalidentity/config/mail.php | 108 +++++ examples/digitalidentity/config/queue.php | 89 ++++ examples/digitalidentity/config/services.php | 33 ++ examples/digitalidentity/config/session.php | 199 ++++++++ examples/digitalidentity/config/view.php | 36 ++ examples/digitalidentity/config/yoti.php | 8 + examples/digitalidentity/docker-compose.yml | 25 ++ examples/digitalidentity/keys/.gitignore | 2 + examples/digitalidentity/public/.htaccess | 21 + .../public/assets/css/index.css | 173 +++++++ .../public/assets/css/profile.css | 425 ++++++++++++++++++ .../public/assets/images/app-store-badge.png | Bin 0 -> 4077 bytes .../assets/images/app-store-badge@2x.png | Bin 0 -> 8819 bytes .../public/assets/images/company-logo.jpg | Bin 0 -> 4682 bytes .../assets/images/google-play-badge.png | Bin 0 -> 4957 bytes .../assets/images/google-play-badge@2x.png | Bin 0 -> 11267 bytes .../public/assets/images/icons/address.svg | 3 + .../public/assets/images/icons/calendar.svg | 5 + .../assets/images/icons/chevron-down-grey.svg | 7 + .../public/assets/images/icons/document.svg | 3 + .../public/assets/images/icons/email.svg | 14 + .../public/assets/images/icons/gender.svg | 5 + .../assets/images/icons/nationality.svg | 3 + .../public/assets/images/icons/phone.svg | 3 + .../public/assets/images/icons/profile.svg | 3 + .../public/assets/images/icons/verified.svg | 6 + .../public/assets/images/logo.png | Bin 0 -> 2988 bytes .../public/assets/images/logo@2x.png | Bin 0 -> 5609 bytes examples/digitalidentity/public/favicon.ico | 0 examples/digitalidentity/public/index.php | 60 +++ examples/digitalidentity/public/robots.txt | 2 + .../resources/views/identity.blade.php | 89 ++++ .../resources/views/partial/address.blade.php | 8 + .../views/partial/ageverification.blade.php | 14 + .../views/partial/attribute.blade.php | 13 + .../views/partial/documentdetails.blade.php | 18 + .../resources/views/partial/report.blade.php | 39 ++ .../resources/views/receipt.blade.php | 104 +++++ examples/digitalidentity/routes/api.php | 19 + examples/digitalidentity/routes/channels.php | 18 + examples/digitalidentity/routes/console.php | 19 + examples/digitalidentity/routes/web.php | 18 + .../digitalidentity/storage/app/.gitignore | 3 + .../storage/app/public/.gitignore | 2 + .../storage/framework/.gitignore | 8 + .../storage/framework/cache/.gitignore | 3 + .../storage/framework/cache/data/.gitignore | 2 + .../storage/framework/sessions/.gitignore | 2 + .../storage/framework/testing/.gitignore | 2 + .../storage/framework/views/.gitignore | 2 + src/DigitalIdentityClient.php | 8 +- .../Util/Attribute/AnchorConverter.php | 4 +- src/Util/Json.php | 18 + 84 files changed, 3420 insertions(+), 3 deletions(-) create mode 100644 examples/digitalidentity/.env.example create mode 100644 examples/digitalidentity/.gitignore create mode 100644 examples/digitalidentity/README.md create mode 100644 examples/digitalidentity/app/Console/Kernel.php create mode 100644 examples/digitalidentity/app/Exceptions/Handler.php create mode 100644 examples/digitalidentity/app/Http/Controllers/IdentityController.php create mode 100644 examples/digitalidentity/app/Http/Controllers/ReceiptController.php create mode 100644 examples/digitalidentity/app/Http/Kernel.php create mode 100644 examples/digitalidentity/app/Http/Middleware/Authenticate.php create mode 100644 examples/digitalidentity/app/Http/Middleware/CheckForMaintenanceMode.php create mode 100644 examples/digitalidentity/app/Http/Middleware/EncryptCookies.php create mode 100644 examples/digitalidentity/app/Http/Middleware/RedirectIfAuthenticated.php create mode 100644 examples/digitalidentity/app/Http/Middleware/TrimStrings.php create mode 100644 examples/digitalidentity/app/Http/Middleware/TrustProxies.php create mode 100644 examples/digitalidentity/app/Http/Middleware/VerifyCsrfToken.php create mode 100644 examples/digitalidentity/app/Providers/RouteServiceProvider.php create mode 100644 examples/digitalidentity/app/Providers/YotiDigitalIdentityServiceProvider.php create mode 100644 examples/digitalidentity/app/Providers/YotiServiceProvider.php create mode 100644 examples/digitalidentity/artisan create mode 100644 examples/digitalidentity/bootstrap/app.php create mode 100644 examples/digitalidentity/bootstrap/cache/.gitignore create mode 100644 examples/digitalidentity/composer.json create mode 100644 examples/digitalidentity/config/app.php create mode 100644 examples/digitalidentity/config/auth.php create mode 100644 examples/digitalidentity/config/broadcasting.php create mode 100644 examples/digitalidentity/config/cache.php create mode 100644 examples/digitalidentity/config/cors.php create mode 100644 examples/digitalidentity/config/database.php create mode 100644 examples/digitalidentity/config/filesystems.php create mode 100644 examples/digitalidentity/config/hashing.php create mode 100644 examples/digitalidentity/config/logging.php create mode 100644 examples/digitalidentity/config/mail.php create mode 100644 examples/digitalidentity/config/queue.php create mode 100644 examples/digitalidentity/config/services.php create mode 100644 examples/digitalidentity/config/session.php create mode 100644 examples/digitalidentity/config/view.php create mode 100644 examples/digitalidentity/config/yoti.php create mode 100644 examples/digitalidentity/docker-compose.yml create mode 100644 examples/digitalidentity/keys/.gitignore create mode 100644 examples/digitalidentity/public/.htaccess create mode 100644 examples/digitalidentity/public/assets/css/index.css create mode 100644 examples/digitalidentity/public/assets/css/profile.css create mode 100644 examples/digitalidentity/public/assets/images/app-store-badge.png create mode 100644 examples/digitalidentity/public/assets/images/app-store-badge@2x.png create mode 100644 examples/digitalidentity/public/assets/images/company-logo.jpg create mode 100644 examples/digitalidentity/public/assets/images/google-play-badge.png create mode 100644 examples/digitalidentity/public/assets/images/google-play-badge@2x.png create mode 100644 examples/digitalidentity/public/assets/images/icons/address.svg create mode 100644 examples/digitalidentity/public/assets/images/icons/calendar.svg create mode 100644 examples/digitalidentity/public/assets/images/icons/chevron-down-grey.svg create mode 100644 examples/digitalidentity/public/assets/images/icons/document.svg create mode 100644 examples/digitalidentity/public/assets/images/icons/email.svg create mode 100644 examples/digitalidentity/public/assets/images/icons/gender.svg create mode 100644 examples/digitalidentity/public/assets/images/icons/nationality.svg create mode 100644 examples/digitalidentity/public/assets/images/icons/phone.svg create mode 100644 examples/digitalidentity/public/assets/images/icons/profile.svg create mode 100644 examples/digitalidentity/public/assets/images/icons/verified.svg create mode 100644 examples/digitalidentity/public/assets/images/logo.png create mode 100644 examples/digitalidentity/public/assets/images/logo@2x.png create mode 100644 examples/digitalidentity/public/favicon.ico create mode 100644 examples/digitalidentity/public/index.php create mode 100644 examples/digitalidentity/public/robots.txt create mode 100644 examples/digitalidentity/resources/views/identity.blade.php create mode 100644 examples/digitalidentity/resources/views/partial/address.blade.php create mode 100644 examples/digitalidentity/resources/views/partial/ageverification.blade.php create mode 100644 examples/digitalidentity/resources/views/partial/attribute.blade.php create mode 100644 examples/digitalidentity/resources/views/partial/documentdetails.blade.php create mode 100644 examples/digitalidentity/resources/views/partial/report.blade.php create mode 100644 examples/digitalidentity/resources/views/receipt.blade.php create mode 100644 examples/digitalidentity/routes/api.php create mode 100644 examples/digitalidentity/routes/channels.php create mode 100644 examples/digitalidentity/routes/console.php create mode 100644 examples/digitalidentity/routes/web.php create mode 100644 examples/digitalidentity/storage/app/.gitignore create mode 100644 examples/digitalidentity/storage/app/public/.gitignore create mode 100644 examples/digitalidentity/storage/framework/.gitignore create mode 100644 examples/digitalidentity/storage/framework/cache/.gitignore create mode 100644 examples/digitalidentity/storage/framework/cache/data/.gitignore create mode 100644 examples/digitalidentity/storage/framework/sessions/.gitignore create mode 100644 examples/digitalidentity/storage/framework/testing/.gitignore create mode 100644 examples/digitalidentity/storage/framework/views/.gitignore diff --git a/examples/digitalidentity/.env.example b/examples/digitalidentity/.env.example new file mode 100644 index 00000000..72022df2 --- /dev/null +++ b/examples/digitalidentity/.env.example @@ -0,0 +1,14 @@ +# This file is a template for defining the environment variables +# Set the application config values here + +YOTI_SDK_ID=xxxxxxxxxxxxxxxxxxxxx + +# Below is the private key (in .pem format) associated with the Yoti Application you created on Yoti Hub +YOTI_KEY_FILE_PATH=./keys/php-sdk-access-security.pem + +# Laravel config: +APP_NAME=yoti.sdk.digitalidentity.demo +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost diff --git a/examples/digitalidentity/.gitignore b/examples/digitalidentity/.gitignore new file mode 100644 index 00000000..4bb28b97 --- /dev/null +++ b/examples/digitalidentity/.gitignore @@ -0,0 +1,16 @@ +/node_modules +/public/hot +/public/storage +/storage/*.key +/vendor +.env +.env.backup +.phpunit.result.cache +Homestead.json +Homestead.yaml +npm-debug.log +yarn-error.log + +*.pem +keys/*.pem +sdk diff --git a/examples/digitalidentity/README.md b/examples/digitalidentity/README.md new file mode 100644 index 00000000..8faa9b32 --- /dev/null +++ b/examples/digitalidentity/README.md @@ -0,0 +1,24 @@ +# Digital Identity Example + +## Requirements + +This example requires [Docker](https://docs.docker.com/) + +## Setup + +* Create your application in the [Yoti Hub](https://hub.yoti.com) (this requires having a Yoti account) + * Set the application domain of your app to `localhost:4002` +* Do the steps below inside the [examples/digitalidentity](./) folder +* Put `your-application-pem-file.pem` file inside the [keys](keys) folder, as Docker requires the `.pem` file to reside within the same location where it's run from. +* Copy `.env.example` to `.env` +* Open `.env` file and fill in the environment variable `YOTI_SDK_ID` + * Set `YOTI_KEY_FILE_PATH` to `./keys/your-application-pem-file.pem` +* Install dependencies `docker-compose up composer` +* Run the `docker-compose up --build` command +* Visit [https://localhost:4002](https://localhost:4002) +* Run the `docker-compose stop` command to stop the containers. + +> To see how to retrieve activity details using the one time use token, refer to the [digitalidentity controller](app/Http/Controllers/IdentityController.php) + +## Digital Identity Example +* Visit [/generate-share](https://localhost:4002/generate-share) diff --git a/examples/digitalidentity/app/Console/Kernel.php b/examples/digitalidentity/app/Console/Kernel.php new file mode 100644 index 00000000..69914e99 --- /dev/null +++ b/examples/digitalidentity/app/Console/Kernel.php @@ -0,0 +1,41 @@ +command('inspire')->hourly(); + } + + /** + * Register the commands for the application. + * + * @return void + */ + protected function commands() + { + $this->load(__DIR__.'/Commands'); + + require base_path('routes/console.php'); + } +} diff --git a/examples/digitalidentity/app/Exceptions/Handler.php b/examples/digitalidentity/app/Exceptions/Handler.php new file mode 100644 index 00000000..59c585dc --- /dev/null +++ b/examples/digitalidentity/app/Exceptions/Handler.php @@ -0,0 +1,55 @@ +withFamilyName() + ->withGivenNames() + ->withFullName() + ->withDateOfBirth() + ->withGender() + ->withNationality() + ->withPhoneNumber() + ->withSelfie() + ->withEmail() + ->withDocumentDetails() + ->withDocumentImages() + ->build(); + + $redirectUri = 'https://host/redirect/'; + + $shareSessionRequest = (new ShareSessionRequestBuilder()) + ->withPolicy($policy) + ->withRedirectUri($redirectUri) + ->build(); + $session = $client->createShareSession($shareSessionRequest); + return $session->getId(); + } + catch (\Throwable $e) { + Log::error($e->getTraceAsString()); + throw new BadRequestHttpException($e->getMessage()); + } + } + public function show(DigitalIdentityClient $client) + { + try { + return view('identity', [ + 'title' => 'Digital Identity Complete Example', + 'sdkId' => $client->id + ]); + } catch (\Throwable $e) { + Log::error($e->getTraceAsString()); + throw new BadRequestHttpException($e->getMessage()); + } + } +} diff --git a/examples/digitalidentity/app/Http/Controllers/ReceiptController.php b/examples/digitalidentity/app/Http/Controllers/ReceiptController.php new file mode 100644 index 00000000..39fe75a6 --- /dev/null +++ b/examples/digitalidentity/app/Http/Controllers/ReceiptController.php @@ -0,0 +1,124 @@ +warning("Unknown Content Type parsing as a String"); + $shareReceipt = $client->fetchShareReceipt($request->query('ReceiptID')); + + $profile = $shareReceipt->getProfile(); + + return view('receipt', [ + 'fullName' => $profile->getFullName(), + 'selfie' => $profile->getSelfie(), + 'profileAttributes' => $this->createAttributesDisplayList($profile), + ]); + } + + /** + * Create attributes display list. + * + * @param UserProfile $profile + * + * @return array + */ + private function createAttributesDisplayList(UserProfile $profile): array + { + $profileAttributes = []; + foreach ($profile->getAttributesList() as $attribute) { + switch ($attribute->getName()) { + case UserProfile::ATTR_SELFIE: + case UserProfile::ATTR_FULL_NAME: + // Selfie and full name are handled separately. + break; + case UserProfile::ATTR_GIVEN_NAMES: + $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Given names', 'yoti-icon-profile'); + break; + case UserProfile::ATTR_FAMILY_NAME: + $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Family names', 'yoti-icon-profile'); + break; + case UserProfile::ATTR_DATE_OF_BIRTH: + $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Date of Birth', 'yoti-icon-calendar'); + break; + case UserProfile::ATTR_GENDER: + $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Gender', 'yoti-icon-gender'); + break; + case UserProfile::ATTR_STRUCTURED_POSTAL_ADDRESS: + $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Structured Postal Address', 'yoti-icon-address'); + break; + case UserProfile::ATTR_POSTAL_ADDRESS: + $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Address', 'yoti-icon-address'); + break; + case UserProfile::ATTR_PHONE_NUMBER: + $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Mobile number', 'yoti-icon-phone'); + break; + case UserProfile::ATTR_NATIONALITY: + $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Nationality', 'yoti-icon-nationality'); + break; + case UserProfile::ATTR_EMAIL_ADDRESS: + $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Email address', 'yoti-icon-email'); + break; + case UserProfile::ATTR_DOCUMENT_DETAILS: + $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Document Details', 'yoti-icon-profile'); + break; + case UserProfile::ATTR_DOCUMENT_IMAGES: + $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Document Images', 'yoti-icon-profile'); + break; + default: + // Skip age verifications (name containing ":"). + if (strpos($attribute->getName(), ':') === false) { + $profileAttributes[] = $this->createAttributeDisplayItem( + $attribute, + ucwords(str_replace('_', ' ', $attribute->getName())), + 'yoti-icon-profile' + ); + } + } + } + + // Add age verifications. + $ageVerifications = $profile->getAgeVerifications(); + if ($ageVerifications) { + foreach ($ageVerifications as $ageVerification) { + $profileAttributes[] = [ + 'name' => 'Age Verification', + 'obj' => $ageVerification->getAttribute(), + 'age_verification' => $ageVerification, + 'icon' => 'yoti-icon-profile', + ]; + } + } + + return $profileAttributes; + } + + /** + * Create attribute display item. + * + * @param Attribute $attribute + * @param string $displayName + * @param string $iconClass + * + * @return array + */ + private function createAttributeDisplayItem(Attribute $attribute, string $displayName, string $iconClass): array + { + return [ + 'name' => $displayName, + 'obj' => $attribute, + 'icon' => $iconClass, + ]; + } +} diff --git a/examples/digitalidentity/app/Http/Kernel.php b/examples/digitalidentity/app/Http/Kernel.php new file mode 100644 index 00000000..c3640f30 --- /dev/null +++ b/examples/digitalidentity/app/Http/Kernel.php @@ -0,0 +1,66 @@ + [ + \App\Http\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + // \Illuminate\Session\Middleware\AuthenticateSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \App\Http\Middleware\VerifyCsrfToken::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], + + 'api' => [ + 'throttle:60,1', + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], + ]; + + /** + * The application's route middleware. + * + * These middleware may be assigned to groups or used individually. + * + * @var array + */ + protected $routeMiddleware = [ + 'auth' => \App\Http\Middleware\Authenticate::class, + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, + 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, + 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, + 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, + 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, + 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + ]; +} diff --git a/examples/digitalidentity/app/Http/Middleware/Authenticate.php b/examples/digitalidentity/app/Http/Middleware/Authenticate.php new file mode 100644 index 00000000..704089a7 --- /dev/null +++ b/examples/digitalidentity/app/Http/Middleware/Authenticate.php @@ -0,0 +1,21 @@ +expectsJson()) { + return route('login'); + } + } +} diff --git a/examples/digitalidentity/app/Http/Middleware/CheckForMaintenanceMode.php b/examples/digitalidentity/app/Http/Middleware/CheckForMaintenanceMode.php new file mode 100644 index 00000000..35b9824b --- /dev/null +++ b/examples/digitalidentity/app/Http/Middleware/CheckForMaintenanceMode.php @@ -0,0 +1,17 @@ +check()) { + return redirect(RouteServiceProvider::HOME); + } + + return $next($request); + } +} diff --git a/examples/digitalidentity/app/Http/Middleware/TrimStrings.php b/examples/digitalidentity/app/Http/Middleware/TrimStrings.php new file mode 100644 index 00000000..5a50e7b5 --- /dev/null +++ b/examples/digitalidentity/app/Http/Middleware/TrimStrings.php @@ -0,0 +1,18 @@ +mapApiRoutes(); + + $this->mapWebRoutes(); + + // + } + + /** + * Define the "web" routes for the application. + * + * These routes all receive session state, CSRF protection, etc. + * + * @return void + */ + protected function mapWebRoutes() + { + Route::middleware('web') + ->namespace($this->namespace) + ->group(base_path('routes/web.php')); + } + + /** + * Define the "api" routes for the application. + * + * These routes are typically stateless. + * + * @return void + */ + protected function mapApiRoutes() + { + Route::prefix('api') + ->middleware('api') + ->namespace($this->namespace) + ->group(base_path('routes/api.php')); + } +} diff --git a/examples/digitalidentity/app/Providers/YotiDigitalIdentityServiceProvider.php b/examples/digitalidentity/app/Providers/YotiDigitalIdentityServiceProvider.php new file mode 100644 index 00000000..f0d3ffa2 --- /dev/null +++ b/examples/digitalidentity/app/Providers/YotiDigitalIdentityServiceProvider.php @@ -0,0 +1,29 @@ +app->singleton(DigitalIdentityClient::class, function ($app) { + $config = $app['config']['yoti']; + return new DigitalIdentityClient($config['client.sdk.id'], $config['pem.file.path']); + }); + } + + /** + * @return array + */ + public function provides() + { + return [DigitalIdentityClient::class]; + } +} diff --git a/examples/digitalidentity/app/Providers/YotiServiceProvider.php b/examples/digitalidentity/app/Providers/YotiServiceProvider.php new file mode 100644 index 00000000..4c357610 --- /dev/null +++ b/examples/digitalidentity/app/Providers/YotiServiceProvider.php @@ -0,0 +1,29 @@ +app->singleton(YotiClient::class, function ($app) { + $config = $app['config']['yoti']; + return new YotiClient($config['client.sdk.id'], $config['pem.file.path']); + }); + } + + /** + * @return array + */ + public function provides() + { + return [YotiClient::class]; + } +} diff --git a/examples/digitalidentity/artisan b/examples/digitalidentity/artisan new file mode 100644 index 00000000..5c23e2e2 --- /dev/null +++ b/examples/digitalidentity/artisan @@ -0,0 +1,53 @@ +#!/usr/bin/env php +make(Illuminate\Contracts\Console\Kernel::class); + +$status = $kernel->handle( + $input = new Symfony\Component\Console\Input\ArgvInput, + new Symfony\Component\Console\Output\ConsoleOutput +); + +/* +|-------------------------------------------------------------------------- +| Shutdown The Application +|-------------------------------------------------------------------------- +| +| Once Artisan has finished running, we will fire off the shutdown events +| so that any final work may be done by the application before we shut +| down the process. This is the last thing to happen to the request. +| +*/ + +$kernel->terminate($input, $status); + +exit($status); diff --git a/examples/digitalidentity/bootstrap/app.php b/examples/digitalidentity/bootstrap/app.php new file mode 100644 index 00000000..037e17df --- /dev/null +++ b/examples/digitalidentity/bootstrap/app.php @@ -0,0 +1,55 @@ +singleton( + Illuminate\Contracts\Http\Kernel::class, + App\Http\Kernel::class +); + +$app->singleton( + Illuminate\Contracts\Console\Kernel::class, + App\Console\Kernel::class +); + +$app->singleton( + Illuminate\Contracts\Debug\ExceptionHandler::class, + App\Exceptions\Handler::class +); + +/* +|-------------------------------------------------------------------------- +| Return The Application +|-------------------------------------------------------------------------- +| +| This script returns the application instance. The instance is given to +| the calling script so we can separate the building of the instances +| from the actual running of the application and sending responses. +| +*/ + +return $app; diff --git a/examples/digitalidentity/bootstrap/cache/.gitignore b/examples/digitalidentity/bootstrap/cache/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/examples/digitalidentity/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/examples/digitalidentity/composer.json b/examples/digitalidentity/composer.json new file mode 100644 index 00000000..f2b24247 --- /dev/null +++ b/examples/digitalidentity/composer.json @@ -0,0 +1,59 @@ +{ + "name": "yoti/yoti-php-sdk-example-digital-identity", + "description": "Yoti SDK Digital Identity Demo", + "license": "MIT", + "require": { + "php": "^8.0", + "fideloper/proxy": "^4.2", + "fruitcake/laravel-cors": "^1.0", + "guzzlehttp/guzzle": "^6.4 || ^7.0", + "laravel/framework": "^8.0", + "laravel/tinker": "^2.3.0", + "yoti/yoti-php-sdk": "^4.0" + }, + "require-dev": { + "facade/ignition": "^2.0" + }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true + }, + "extra": { + "laravel": { + "dont-discover": [] + } + }, + "autoload": { + "psr-4": { + "App\\": "app/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "post-autoload-dump": [ + "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", + "@php artisan package:discover --ansi" + ], + "post-update-cmd": "@php artisan key:generate --ansi", + "copy-sdk": "grep -q 'yoti-php-sdk' ../../composer.json && rm -fr ./sdk && cd ../../ && git archive --prefix=sdk/ --format=tar HEAD | (cd - && tar xf -) || echo 'Could not install SDK from parent directory'", + "install-local": [ + "@copy-sdk", + "composer install" + ], + "update-local": [ + "@copy-sdk", + "composer update" + ] + }, + "repositories": [ + { + "type": "path", + "url": "./sdk", + "options": { + "symlink": true + } + } + ] +} diff --git a/examples/digitalidentity/config/app.php b/examples/digitalidentity/config/app.php new file mode 100644 index 00000000..1a57883e --- /dev/null +++ b/examples/digitalidentity/config/app.php @@ -0,0 +1,229 @@ + env('APP_NAME', 'Laravel'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | your application so that it is used when running Artisan tasks. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + 'asset_url' => env('ASSET_URL', null), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. We have gone + | ahead and set this to a sensible default for you out of the box. + | + */ + + 'timezone' => 'UTC', + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by the translation service provider. You are free to set this value + | to any of the locales which will be supported by the application. + | + */ + + 'locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Application Fallback Locale + |-------------------------------------------------------------------------- + | + | The fallback locale determines the locale to use when the current one + | is not available. You may change the value to correspond to any of + | the language folders that are provided through your application. + | + */ + + 'fallback_locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Faker Locale + |-------------------------------------------------------------------------- + | + | This locale will be used by the Faker PHP library when generating fake + | data for your database seeds. For example, this will be used to get + | localized telephone numbers, street address information and more. + | + */ + + 'faker_locale' => 'en_US', + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is used by the Illuminate encrypter service and should be set + | to a random, 32 character string, otherwise these encrypted strings + | will not be safe. Please do this before deploying an application! + | + */ + + 'key' => env('APP_KEY'), + + 'cipher' => 'AES-256-CBC', + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + | + | The service providers listed here will be automatically loaded on the + | request to your application. Feel free to add your own services to + | this array to grant expanded functionality to your applications. + | + */ + + 'providers' => [ + + /* + * Laravel Framework Service Providers... + */ + Illuminate\Auth\AuthServiceProvider::class, + Illuminate\Broadcasting\BroadcastServiceProvider::class, + Illuminate\Bus\BusServiceProvider::class, + Illuminate\Cache\CacheServiceProvider::class, + Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, + Illuminate\Cookie\CookieServiceProvider::class, + Illuminate\Database\DatabaseServiceProvider::class, + Illuminate\Encryption\EncryptionServiceProvider::class, + Illuminate\Filesystem\FilesystemServiceProvider::class, + Illuminate\Foundation\Providers\FoundationServiceProvider::class, + Illuminate\Hashing\HashServiceProvider::class, + Illuminate\Mail\MailServiceProvider::class, + Illuminate\Notifications\NotificationServiceProvider::class, + Illuminate\Pagination\PaginationServiceProvider::class, + Illuminate\Pipeline\PipelineServiceProvider::class, + Illuminate\Queue\QueueServiceProvider::class, + Illuminate\Redis\RedisServiceProvider::class, + Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, + Illuminate\Session\SessionServiceProvider::class, + Illuminate\Translation\TranslationServiceProvider::class, + Illuminate\Validation\ValidationServiceProvider::class, + Illuminate\View\ViewServiceProvider::class, + + /* + * Package Service Providers... + */ + + /* + * Application Service Providers... + */ + App\Providers\YotiServiceProvider::class, + App\Providers\YotiDigitalIdentityServiceProvider::class, + App\Providers\RouteServiceProvider::class, + ], + + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. However, feel free to register as many as you wish as + | the aliases are "lazy" loaded so they don't hinder performance. + | + */ + + 'aliases' => [ + + 'App' => Illuminate\Support\Facades\App::class, + 'Arr' => Illuminate\Support\Arr::class, + 'Artisan' => Illuminate\Support\Facades\Artisan::class, + 'Auth' => Illuminate\Support\Facades\Auth::class, + 'Blade' => Illuminate\Support\Facades\Blade::class, + 'Broadcast' => Illuminate\Support\Facades\Broadcast::class, + 'Bus' => Illuminate\Support\Facades\Bus::class, + 'Cache' => Illuminate\Support\Facades\Cache::class, + 'Config' => Illuminate\Support\Facades\Config::class, + 'Cookie' => Illuminate\Support\Facades\Cookie::class, + 'Crypt' => Illuminate\Support\Facades\Crypt::class, + 'DB' => Illuminate\Support\Facades\DB::class, + 'Eloquent' => Illuminate\Database\Eloquent\Model::class, + 'Event' => Illuminate\Support\Facades\Event::class, + 'File' => Illuminate\Support\Facades\File::class, + 'Gate' => Illuminate\Support\Facades\Gate::class, + 'Hash' => Illuminate\Support\Facades\Hash::class, + 'Http' => Illuminate\Support\Facades\Http::class, + 'Lang' => Illuminate\Support\Facades\Lang::class, + 'Log' => Illuminate\Support\Facades\Log::class, + 'Mail' => Illuminate\Support\Facades\Mail::class, + 'Notification' => Illuminate\Support\Facades\Notification::class, + 'Password' => Illuminate\Support\Facades\Password::class, + 'Queue' => Illuminate\Support\Facades\Queue::class, + 'Redirect' => Illuminate\Support\Facades\Redirect::class, + 'Redis' => Illuminate\Support\Facades\Redis::class, + 'Request' => Illuminate\Support\Facades\Request::class, + 'Response' => Illuminate\Support\Facades\Response::class, + 'Route' => Illuminate\Support\Facades\Route::class, + 'Schema' => Illuminate\Support\Facades\Schema::class, + 'Session' => Illuminate\Support\Facades\Session::class, + 'Storage' => Illuminate\Support\Facades\Storage::class, + 'Str' => Illuminate\Support\Str::class, + 'URL' => Illuminate\Support\Facades\URL::class, + 'Validator' => Illuminate\Support\Facades\Validator::class, + 'View' => Illuminate\Support\Facades\View::class, + + ], + +]; diff --git a/examples/digitalidentity/config/auth.php b/examples/digitalidentity/config/auth.php new file mode 100644 index 00000000..aaf982bc --- /dev/null +++ b/examples/digitalidentity/config/auth.php @@ -0,0 +1,117 @@ + [ + 'guard' => 'web', + 'passwords' => 'users', + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | here which uses session storage and the Eloquent user provider. + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | Supported: "session", "token" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + + 'api' => [ + 'driver' => 'token', + 'provider' => 'users', + 'hash' => false, + ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | If you have multiple user tables or models you may configure multiple + | sources which represent each model / table. These sources may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ + + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => App\User::class, + ], + + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | You may specify multiple password reset configurations if you have more + | than one user table or model in the application and you want to have + | separate password reset settings based on the specific user types. + | + | The expire time is the number of minutes that the reset token should be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + */ + + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => 'password_resets', + 'expire' => 60, + 'throttle' => 60, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the amount of seconds before a password confirmation + | times out and the user is prompted to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ + + 'password_timeout' => 10800, + +]; diff --git a/examples/digitalidentity/config/broadcasting.php b/examples/digitalidentity/config/broadcasting.php new file mode 100644 index 00000000..3bba1103 --- /dev/null +++ b/examples/digitalidentity/config/broadcasting.php @@ -0,0 +1,59 @@ + env('BROADCAST_DRIVER', 'null'), + + /* + |-------------------------------------------------------------------------- + | Broadcast Connections + |-------------------------------------------------------------------------- + | + | Here you may define all of the broadcast connections that will be used + | to broadcast events to other systems or over websockets. Samples of + | each available type of connection are provided inside this array. + | + */ + + 'connections' => [ + + 'pusher' => [ + 'driver' => 'pusher', + 'key' => env('PUSHER_APP_KEY'), + 'secret' => env('PUSHER_APP_SECRET'), + 'app_id' => env('PUSHER_APP_ID'), + 'options' => [ + 'cluster' => env('PUSHER_APP_CLUSTER'), + 'useTLS' => true, + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'default', + ], + + 'log' => [ + 'driver' => 'log', + ], + + 'null' => [ + 'driver' => 'null', + ], + + ], + +]; diff --git a/examples/digitalidentity/config/cache.php b/examples/digitalidentity/config/cache.php new file mode 100644 index 00000000..4f41fdf9 --- /dev/null +++ b/examples/digitalidentity/config/cache.php @@ -0,0 +1,104 @@ + env('CACHE_DRIVER', 'file'), + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + | + | Here you may define all of the cache "stores" for your application as + | well as their drivers. You may even define multiple stores for the + | same cache driver to group types of items stored in your caches. + | + */ + + 'stores' => [ + + 'apc' => [ + 'driver' => 'apc', + ], + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + + 'database' => [ + 'driver' => 'database', + 'table' => 'cache', + 'connection' => null, + ], + + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache/data'), + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + // Memcached::OPT_CONNECT_TIMEOUT => 2000, + ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, + ], + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'cache', + ], + + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing a RAM based store such as APC or Memcached, there might + | be other applications utilizing the same cache. So, we'll specify a + | value to get prefixed to all our keys so we can avoid collisions. + | + */ + + 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'), + +]; diff --git a/examples/digitalidentity/config/cors.php b/examples/digitalidentity/config/cors.php new file mode 100644 index 00000000..558369dc --- /dev/null +++ b/examples/digitalidentity/config/cors.php @@ -0,0 +1,34 @@ + ['api/*'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => false, + +]; diff --git a/examples/digitalidentity/config/database.php b/examples/digitalidentity/config/database.php new file mode 100644 index 00000000..b42d9b30 --- /dev/null +++ b/examples/digitalidentity/config/database.php @@ -0,0 +1,147 @@ + env('DB_CONNECTION', 'mysql'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to make development simple. + | + | + | All database work in Laravel is done through the PHP PDO facilities + | so make sure you have the driver for your particular database of + | choice installed on your machine before you begin development. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'url' => env('DATABASE_URL'), + 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + 'schema' => 'public', + 'sslmode' => 'prefer', + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '1433'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run in the database. + | + */ + + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer body of commands than a typical key-value system + | such as APC or Memcached. Laravel makes it easy to dig right in. + | + */ + + 'redis' => [ + + 'client' => env('REDIS_CLIENT', 'phpredis'), + + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), + ], + + 'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), + ], + + 'cache' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_CACHE_DB', '1'), + ], + + ], + +]; diff --git a/examples/digitalidentity/config/filesystems.php b/examples/digitalidentity/config/filesystems.php new file mode 100644 index 00000000..cd9f0962 --- /dev/null +++ b/examples/digitalidentity/config/filesystems.php @@ -0,0 +1,84 @@ + env('FILESYSTEM_DRIVER', 'local'), + + /* + |-------------------------------------------------------------------------- + | Default Cloud Filesystem Disk + |-------------------------------------------------------------------------- + | + | Many applications store files both locally and in the cloud. For this + | reason, you may specify a default "cloud" driver here. This driver + | will be bound as the Cloud disk implementation in the container. + | + */ + + 'cloud' => env('FILESYSTEM_CLOUD', 's3'), + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + | + | Here you may configure as many filesystem "disks" as you wish, and you + | may even configure multiple disks of the same driver. Defaults have + | been setup for each driver as an example of the required options. + | + | Supported Drivers: "local", "ftp", "sftp", "s3" + | + */ + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app'), + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + ], + + 's3' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Symbolic Links + |-------------------------------------------------------------------------- + | + | Here you may configure the symbolic links that will be created when the + | `storage:link` Artisan command is executed. The array keys should be + | the locations of the links and the values should be their targets. + | + */ + + 'links' => [ + public_path('storage') => storage_path('app/public'), + ], + +]; diff --git a/examples/digitalidentity/config/hashing.php b/examples/digitalidentity/config/hashing.php new file mode 100644 index 00000000..84257708 --- /dev/null +++ b/examples/digitalidentity/config/hashing.php @@ -0,0 +1,52 @@ + 'bcrypt', + + /* + |-------------------------------------------------------------------------- + | Bcrypt Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Bcrypt algorithm. This will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'bcrypt' => [ + 'rounds' => env('BCRYPT_ROUNDS', 10), + ], + + /* + |-------------------------------------------------------------------------- + | Argon Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Argon algorithm. These will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'argon' => [ + 'memory' => 1024, + 'threads' => 2, + 'time' => 2, + ], + +]; diff --git a/examples/digitalidentity/config/logging.php b/examples/digitalidentity/config/logging.php new file mode 100644 index 00000000..088c204e --- /dev/null +++ b/examples/digitalidentity/config/logging.php @@ -0,0 +1,104 @@ + env('LOG_CHANNEL', 'stack'), + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Out of + | the box, Laravel uses the Monolog PHP logging library. This gives + | you a variety of powerful log handlers / formatters to utilize. + | + | Available Drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", + | "custom", "stack" + | + */ + + 'channels' => [ + 'stack' => [ + 'driver' => 'stack', + 'channels' => ['single'], + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => 'debug', + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => 'debug', + 'days' => 14, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => 'Laravel Log', + 'emoji' => ':boom:', + 'level' => 'critical', + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => 'debug', + 'handler' => SyslogUdpHandler::class, + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + ], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'handler' => StreamHandler::class, + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'with' => [ + 'stream' => 'php://stderr', + ], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => 'debug', + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => 'debug', + ], + + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], + ], + +]; diff --git a/examples/digitalidentity/config/mail.php b/examples/digitalidentity/config/mail.php new file mode 100644 index 00000000..cfef410f --- /dev/null +++ b/examples/digitalidentity/config/mail.php @@ -0,0 +1,108 @@ + env('MAIL_MAILER', 'smtp'), + + /* + |-------------------------------------------------------------------------- + | Mailer Configurations + |-------------------------------------------------------------------------- + | + | Here you may configure all of the mailers used by your application plus + | their respective settings. Several examples have been configured for + | you and you are free to add your own as your application requires. + | + | Laravel supports a variety of mail "transport" drivers to be used while + | sending an e-mail. You will specify which one you are using for your + | mailers below. You are free to add additional mailers as required. + | + | Supported: "smtp", "sendmail", "mailgun", "ses", + | "postmark", "log", "array" + | + */ + + 'mailers' => [ + 'smtp' => [ + 'transport' => 'smtp', + 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), + 'port' => env('MAIL_PORT', 587), + 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + ], + + 'ses' => [ + 'transport' => 'ses', + ], + + 'mailgun' => [ + 'transport' => 'mailgun', + ], + + 'postmark' => [ + 'transport' => 'postmark', + ], + + 'sendmail' => [ + 'transport' => 'sendmail', + 'path' => '/usr/sbin/sendmail -bs', + ], + + 'log' => [ + 'transport' => 'log', + 'channel' => env('MAIL_LOG_CHANNEL'), + ], + + 'array' => [ + 'transport' => 'array', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all e-mails sent by your application to be sent from + | the same address. Here, you may specify a name and address that is + | used globally for all e-mails that are sent by your application. + | + */ + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + + /* + |-------------------------------------------------------------------------- + | Markdown Mail Settings + |-------------------------------------------------------------------------- + | + | If you are using Markdown based email rendering, you may configure your + | theme and component paths here, allowing you to customize the design + | of the emails. Or, you may simply stick with the Laravel defaults! + | + */ + + 'markdown' => [ + 'theme' => 'default', + + 'paths' => [ + resource_path('views/vendor/mail'), + ], + ], + +]; diff --git a/examples/digitalidentity/config/queue.php b/examples/digitalidentity/config/queue.php new file mode 100644 index 00000000..00b76d65 --- /dev/null +++ b/examples/digitalidentity/config/queue.php @@ -0,0 +1,89 @@ + env('QUEUE_CONNECTION', 'sync'), + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection information for each server that + | is used by your application. A default configuration has been added + | for each back-end shipped with Laravel. You are free to add more. + | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" + | + */ + + 'connections' => [ + + 'sync' => [ + 'driver' => 'sync', + ], + + 'database' => [ + 'driver' => 'database', + 'table' => 'jobs', + 'queue' => 'default', + 'retry_after' => 90, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => 'localhost', + 'queue' => 'default', + 'retry_after' => 90, + 'block_for' => 0, + ], + + 'sqs' => [ + 'driver' => 'sqs', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'your-queue-name'), + 'suffix' => env('SQS_SUFFIX'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'default', + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => 90, + 'block_for' => null, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control which database and table are used to store the jobs that + | have failed. You may change them to any database / table you wish. + | + */ + + 'failed' => [ + 'driver' => env('QUEUE_FAILED_DRIVER', 'database'), + 'database' => env('DB_CONNECTION', 'mysql'), + 'table' => 'failed_jobs', + ], + +]; diff --git a/examples/digitalidentity/config/services.php b/examples/digitalidentity/config/services.php new file mode 100644 index 00000000..2a1d616c --- /dev/null +++ b/examples/digitalidentity/config/services.php @@ -0,0 +1,33 @@ + [ + 'domain' => env('MAILGUN_DOMAIN'), + 'secret' => env('MAILGUN_SECRET'), + 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), + ], + + 'postmark' => [ + 'token' => env('POSTMARK_TOKEN'), + ], + + 'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + ], + +]; diff --git a/examples/digitalidentity/config/session.php b/examples/digitalidentity/config/session.php new file mode 100644 index 00000000..d0ccd5a8 --- /dev/null +++ b/examples/digitalidentity/config/session.php @@ -0,0 +1,199 @@ + env('SESSION_DRIVER', 'file'), + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle before it expires. If you want them + | to immediately expire on the browser closing, set that option. + | + */ + + 'lifetime' => env('SESSION_LIFETIME', 120), + + 'expire_on_close' => false, + + /* + |-------------------------------------------------------------------------- + | Session Encryption + |-------------------------------------------------------------------------- + | + | This option allows you to easily specify that all of your session data + | should be encrypted before it is stored. All encryption will be run + | automatically by Laravel and you can use the Session like normal. + | + */ + + 'encrypt' => false, + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When using the native session driver, we need a location where session + | files may be stored. A default has been set for you but a different + | location may be specified. This is only needed for file sessions. + | + */ + + 'files' => storage_path('framework/sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" or "redis" session drivers, you may specify a + | connection that should be used to manage these sessions. This should + | correspond to a connection in your database configuration options. + | + */ + + 'connection' => env('SESSION_CONNECTION', null), + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table we + | should use to manage the sessions. Of course, a sensible default is + | provided for you; however, you are free to change this as needed. + | + */ + + 'table' => 'sessions', + + /* + |-------------------------------------------------------------------------- + | Session Cache Store + |-------------------------------------------------------------------------- + | + | When using the "apc", "memcached", or "dynamodb" session drivers you may + | list a cache store that should be used for these sessions. This value + | must match with one of the application's configured cache "stores". + | + */ + + 'store' => env('SESSION_STORE', null), + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => [2, 100], + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the cookie used to identify a session + | instance by ID. The name specified here will get used every time a + | new session cookie is created by the framework for every driver. + | + */ + + 'cookie' => env( + 'SESSION_COOKIE', + Str::slug(env('APP_NAME', 'laravel'), '_').'_session' + ), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Path + |-------------------------------------------------------------------------- + | + | The session cookie path determines the path for which the cookie will + | be regarded as available. Typically, this will be the root path of + | your application but you are free to change this when necessary. + | + */ + + 'path' => '/', + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | Here you may change the domain of the cookie used to identify a session + | in your application. This will determine which domains the cookie is + | available to in your application. A sensible default has been set. + | + */ + + 'domain' => env('SESSION_DOMAIN', null), + + /* + |-------------------------------------------------------------------------- + | HTTPS Only Cookies + |-------------------------------------------------------------------------- + | + | By setting this option to true, session cookies will only be sent back + | to the server if the browser has a HTTPS connection. This will keep + | the cookie from being sent to you if it can not be done securely. + | + */ + + 'secure' => env('SESSION_SECURE_COOKIE'), + + /* + |-------------------------------------------------------------------------- + | HTTP Access Only + |-------------------------------------------------------------------------- + | + | Setting this value to true will prevent JavaScript from accessing the + | value of the cookie and the cookie will only be accessible through + | the HTTP protocol. You are free to modify this option if needed. + | + */ + + 'http_only' => true, + + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | This option determines how your cookies behave when cross-site requests + | take place, and can be used to mitigate CSRF attacks. By default, we + | do not enable this as other CSRF protection services are in place. + | + | Supported: "lax", "strict", "none", null + | + */ + + 'same_site' => 'lax', + +]; diff --git a/examples/digitalidentity/config/view.php b/examples/digitalidentity/config/view.php new file mode 100644 index 00000000..22b8a18d --- /dev/null +++ b/examples/digitalidentity/config/view.php @@ -0,0 +1,36 @@ + [ + resource_path('views'), + ], + + /* + |-------------------------------------------------------------------------- + | Compiled View Path + |-------------------------------------------------------------------------- + | + | This option determines where all the compiled Blade templates will be + | stored for your application. Typically, this is within the storage + | directory. However, as usual, you are free to change this value. + | + */ + + 'compiled' => env( + 'VIEW_COMPILED_PATH', + realpath(storage_path('framework/views')) + ), + +]; diff --git a/examples/digitalidentity/config/yoti.php b/examples/digitalidentity/config/yoti.php new file mode 100644 index 00000000..d5f6e761 --- /dev/null +++ b/examples/digitalidentity/config/yoti.php @@ -0,0 +1,8 @@ + env('YOTI_SDK_ID'), + 'pem.file.path' => (function($filePath) { + return strpos($filePath, '/') === 0 ? $filePath : base_path($filePath); + })(env('YOTI_KEY_FILE_PATH')), +]; diff --git a/examples/digitalidentity/docker-compose.yml b/examples/digitalidentity/docker-compose.yml new file mode 100644 index 00000000..7b5d8840 --- /dev/null +++ b/examples/digitalidentity/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3' + +services: + web: + build: ../docker + ports: + - "4002:443" + volumes: + - ./:/usr/share/nginx/html + links: + - php + + php: + build: + context: ../docker + dockerfile: php.dockerfile + volumes: + - ./:/usr/share/nginx/html + + composer: + image: composer + volumes: + - ../../:/usr/share/yoti-php-sdk + working_dir: /usr/share/yoti-php-sdk/examples/digitalidentity + command: update-local diff --git a/examples/digitalidentity/keys/.gitignore b/examples/digitalidentity/keys/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/examples/digitalidentity/keys/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/examples/digitalidentity/public/.htaccess b/examples/digitalidentity/public/.htaccess new file mode 100644 index 00000000..3aec5e27 --- /dev/null +++ b/examples/digitalidentity/public/.htaccess @@ -0,0 +1,21 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Redirect Trailing Slashes If Not A Folder... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] + + # Send Requests To Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + diff --git a/examples/digitalidentity/public/assets/css/index.css b/examples/digitalidentity/public/assets/css/index.css new file mode 100644 index 00000000..14a2bc8c --- /dev/null +++ b/examples/digitalidentity/public/assets/css/index.css @@ -0,0 +1,173 @@ +.yoti-body { + margin: 0; +} + +.yoti-top-section { + display: flex; + flex-direction: column; + + padding: 38px 0; + + background-color: #f7f8f9; + + align-items: center; +} + +.yoti-logo-section { + margin-bottom: 25px; +} + +.yoti-logo-image { + display: block; +} + +.yoti-top-header { + font-family: Roboto, sans-serif; + font-size: 40px; + font-weight: 700; + line-height: 1.2; + margin-top: 0; + margin-bottom: 80px; + text-align: center; + + color: #000; +} + +@media (min-width: 600px) { + .yoti-top-header { + line-height: 1.4; + } +} + +.yoti-sdk-integration-section { + margin: 30px 0; +} + +#yoti-share-button { + width: 250px; + height: 45px; +} + +.yoti-login-or-separator { + text-transform: uppercase; + font-family: Roboto; + font-size: 16px; + font-weight: bold; + line-height: 1.5; + text-align: center; + margin-top: 30px; +} + +.yoti-login-dialog { + display: grid; + + box-sizing: border-box; + width: 100%; + padding: 35px 38px; + + border-radius: 5px; + background: #fff; + + grid-gap: 25px; +} + +@media (min-width: 600px) { + .yoti-login-dialog { + width: 560px; + padding: 35px 88px; + } +} + +.yoti-login-dialog-header { + font-family: Roboto, sans-serif; + font-size: 24px; + font-weight: 700; + line-height: 1.1; + + margin: 0; + + color: #000; +} + +.yoti-input { + font-family: Roboto, sans-serif; + font-size: 16px; + line-height: 1.5; + + box-sizing: border-box; + padding: 12px 15px; + + color: #000; + border: solid 2px #000; + border-radius: 4px; + background-color: #fff; +} + +.yoti-login-actions { + display: flex; + + justify-content: space-between; + align-items: center; +} + +.yoti-login-forgot-button { + font-family: Roboto, sans-serif; + font-size: 16px; + + text-transform: capitalize; +} + +.yoti-login-button { + font-family: Roboto, sans-serif; + font-size: 16px; + + box-sizing: border-box; + width: 145px; + height: 50px; + + text-transform: uppercase; + + color: #fff; + border: 0; + background-color: #000; +} + +.yoti-sponsor-app-section { + display: flex; + flex-direction: column; + + padding: 70px 0; + + align-items: center; +} + +.yoti-sponsor-app-header { + font-family: Roboto, sans-serif; + font-size: 20px; + font-weight: 700; + line-height: 1.2; + + margin: 0; + + text-align: center; + + color: #000; +} + +.yoti-store-buttons-section { + margin-top: 40px; + display: grid; + grid-gap: 10px; + grid-template-columns: 1fr; +} + +@media (min-width: 600px) { + .yoti-store-buttons-section { + grid-template-columns: 1fr 1fr; + grid-gap: 25px; + } +} + +.yoti-app-button-link { + text-decoration: none; +} \ No newline at end of file diff --git a/examples/digitalidentity/public/assets/css/profile.css b/examples/digitalidentity/public/assets/css/profile.css new file mode 100644 index 00000000..9d43066d --- /dev/null +++ b/examples/digitalidentity/public/assets/css/profile.css @@ -0,0 +1,425 @@ +.yoti-html { + height: 100%; +} + +.yoti-body { + margin: 0; + height: 100%; +} + +.yoti-icon-profile, +.yoti-icon-phone, +.yoti-icon-email, +.yoti-icon-calendar, +.yoti-icon-verified, +.yoti-icon-address, +.yoti-icon-gender, +.yoti-icon-nationality { + display: inline-block; + height: 28px; + width: 28px; + flex-shrink: 0; +} + +.yoti-icon-profile { + background: no-repeat url('/assets/images/icons/profile.svg'); +} + +.yoti-icon-phone { + background: no-repeat url('/assets/images/icons/phone.svg'); +} + +.yoti-icon-email { + background: no-repeat url('/assets/images/icons/email.svg'); +} + +.yoti-icon-calendar { + background: no-repeat url('/assets/images/icons/calendar.svg'); +} + +.yoti-icon-verified { + background: no-repeat url('/assets/images/icons/verified.svg'); +} + +.yoti-icon-address { + background: no-repeat url('/assets/images/icons/address.svg'); +} + +.yoti-icon-gender { + background: no-repeat url('/assets/images/icons/gender.svg'); +} + +.yoti-icon-nationality { + background: no-repeat url('/assets/images/icons/nationality.svg'); +} + +.yoti-profile-layout { + display: grid; + grid-template-columns: 1fr; +} + +@media (min-width: 1100px) { + .yoti-profile-layout { + grid-template-columns: 360px 1fr; + height: 100%; + } +} + +.yoti-profile-user-section { + display: flex; + align-items: center; + justify-content: space-between; + flex-direction: column; + padding: 40px 0; + background-color: #f7f8f9; +} + +@media (min-width: 1100px) { + .yoti-profile-user-section { + display: grid; + grid-template-rows: repeat(3, min-content); + align-items: center; + justify-content: center; + position: relative; + } +} + +.yoti-profile-picture-image { + width: 220px; + height: 220px; + border-radius: 50%; +} + +.yoti-profile-picture-powered, +.yoti-profile-picture-account-creation { + font-family: Roboto; + font-size: 14px; + color: #b6bfcb; +} + +.yoti-profile-picture-powered-section { + display: flex; + flex-direction: column; + text-align: center; + align-items: center; +} + +@media (min-width: 1100px) { + .yoti-profile-picture-powered-section { + align-self: start; + } +} + +.yoti-profile-picture-powered { + margin-bottom: 20px; +} + +.yoti-profile-picture-section { + display: flex; + flex-direction: column; + align-items: center; +} + +@media (min-width: 1100px) { + .yoti-profile-picture-section { + position: absolute; + top: 50%; + transform: translateY(-50%); + width: 100%; + } +} + +.yoti-logo-image { + margin-bottom: 25px; +} + +.yoti-profile-picture-area { + position: relative; + display: inline-block; +} + +.yoti-profile-picture-verified-icon { + display: block; + background: no-repeat url("/assets/images/icons/verified.svg"); + background-size: cover; + height: 40px; + width: 40px; + position: absolute; + top: 10px; + right: 10px; +} + +.yoti-profile-name { + margin-top: 20px; + font-family: Roboto, sans-serif; + font-size: 24px; + text-align: center; + color: #333b40; +} + +.yoti-attributes-section { + display: flex; + flex-direction: column; + justify-content: start; + align-items: center; + + width: 100%; + padding: 40px 0; +} + +.yoti-attributes-section.-condensed { + padding: 0; +} + +@media (min-width: 1100px) { + .yoti-attributes-section { + padding: 60px 0; + align-items: start; + overflow-y: scroll; + } + + .yoti-attributes-section.-condensed { + padding: 0; + } +} + +.yoti-company-logo { + margin-bottom: 40px; +} + +@media (min-width: 1100px) { + .yoti-company-logo { + margin-left: 130px; + } +} + +/* extended layout list */ +.yoti-attribute-list-header, +.yoti-attribute-list-subheader { + display: none; +} + +@media (min-width: 1100px) { + .yoti-attribute-list-header, + .yoti-attribute-list-subheader { + width: 100%; + + display: grid; + grid-template-columns: 200px 1fr 1fr; + grid-template-rows: 40px; + + align-items: center; + text-align: center; + + font-family: Roboto; + font-size: 14px; + color: #b6bfcb; + } +} + +.yoti-attribute-list-header-attribute, +.yoti-attribute-list-header-value { + justify-self: start; + padding: 0 20px; +} + +.yoti-attribute-list-subheader { + grid-template-rows: 30px; +} + +.yoti-attribute-list-subhead-layout { + grid-column: 3; + display: grid; + grid-template-columns: 1fr 1fr 1fr; +} + +.yoti-attribute-list { + display: grid; + width: 100%; +} + +.yoti-attribute-list-item:first-child { + border-top: 2px solid #f7f8f9; +} + +.yoti-attribute-list-item { + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: minmax(60px, auto); + border-bottom: 2px solid #f7f8f9; + border-right: none; + border-left: none; +} + +.yoti-attribute-list-item.-condensed { + grid-template-columns: 50% 50%; + padding: 5px 35px; +} + +@media (min-width: 1100px) { + .yoti-attribute-list-item { + display: grid; + grid-template-columns: 200px 1fr 1fr; + grid-template-rows: minmax(80px, auto); + } + + .yoti-attribute-list-item.-condensed { + grid-template-columns: 200px 1fr; + padding: 0 75px; + } +} + +.yoti-attribute-cell { + display: flex; + align-items: center; +} + +.yoti-attribute-name { + grid-column: 1 / 2; + + display: flex; + align-items: center; + justify-content: center; + + border-right: 2px solid #f7f8f9; + + padding: 20px; +} + +@media (min-width: 1100px) { + .yoti-attribute-name { + justify-content: start; + } +} + +.yoti-attribute-name.-condensed { + justify-content: start; +} + +.yoti-attribute-name-cell { + display: flex; + align-items: center; +} + +.yoti-attribute-name-cell-text { + font-family: Roboto, sans-serif; + font-size: 16px; + color: #b6bfcb; + margin-left: 12px; +} + +.yoti-attribute-value-text table { + font-size: 14px; + border-spacing: 0; +} + +.yoti-attribute-value-text table td:first-child { + font-weight: bold; +} + +.yoti-attribute-value-text table td { + border-bottom: 1px solid #f7f8f9; + padding: 5px; +} + +.yoti-attribute-value-text img { + width: 100%; +} + +.yoti-attribute-value { + grid-column: 2 / 3; + + display: flex; + align-items: center; + justify-content: center; + + padding: 20px; +} + +@media (min-width: 1100px) { + .yoti-attribute-value { + justify-content: start; + } +} + +.yoti-attribute-value.-condensed { + justify-content: start; +} + +.yoti-attribute-value-text { + font-family: Roboto, sans-serif; + font-size: 18px; + color: #333b40; + word-break: break-word; +} + +.yoti-attribute-anchors-layout { + grid-column: 1 / 3; + grid-row: 2 / 2; + + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-auto-rows: minmax(40px, auto); + font-family: Roboto, sans-serif; + font-size: 14px; + + background-color: #f7f8f9; +} + +@media (min-width: 1100px) { + .yoti-attribute-anchors-layout { + grid-column: 3 / 4; + grid-row: 1 / 2; + } +} + +.yoti-attribute-anchors-head { + border-bottom: 1px solid #dde2e5; + display: flex; + align-items: center; + justify-content: center; +} + +@media (min-width: 1100px) { + .yoti-attribute-anchors-head { + display: none; + } +} + +.yoti-attribute-anchors { + display: flex; + align-items: center; + justify-content: center; +} + +.yoti-attribute-anchors-head.-s-v { + grid-column-start: span 1 s-v; +} +.yoti-attribute-anchors-head.-value { + grid-column-start: span 1 value; +} +.yoti-attribute-anchors-head.-subtype { + grid-column-start: span 1 subtype; +} + +.yoti-attribute-anchors.-s-v { + grid-column-start: span 1 s-v; +} + +.yoti-attribute-anchors.-value { + grid-column-start: span 1 value; +} + +.yoti-attribute-anchors.-subtype { + grid-column-start: span 1 subtype; +} + +.yoti-edit-section { + padding: 50px 20px; +} + +@media (min-width: 1100px) { + .yoti-edit-section { + padding: 75px 110px; + } +} diff --git a/examples/digitalidentity/public/assets/images/app-store-badge.png b/examples/digitalidentity/public/assets/images/app-store-badge.png new file mode 100644 index 0000000000000000000000000000000000000000..3ec996cc6288d68279c1d735c9d627c64d8a48c6 GIT binary patch literal 4077 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 literal 0 HcmV?d00001 diff --git a/examples/digitalidentity/public/assets/images/app-store-badge@2x.png b/examples/digitalidentity/public/assets/images/app-store-badge@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..84b34068fc22aa74f388f6db1a583ac6592f5420 GIT binary patch literal 8819 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 MlZs4*lPCscyWU2PMP1t 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 + + diff --git a/examples/digitalidentity/public/assets/images/icons/calendar.svg b/examples/digitalidentity/public/assets/images/icons/calendar.svg new file mode 100644 index 00000000..4f6b9bb7 --- /dev/null +++ b/examples/digitalidentity/public/assets/images/icons/calendar.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/digitalidentity/public/assets/images/icons/chevron-down-grey.svg b/examples/digitalidentity/public/assets/images/icons/chevron-down-grey.svg new file mode 100644 index 00000000..6753becb --- /dev/null +++ b/examples/digitalidentity/public/assets/images/icons/chevron-down-grey.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/digitalidentity/public/assets/images/icons/document.svg b/examples/digitalidentity/public/assets/images/icons/document.svg new file mode 100644 index 00000000..4c41271e --- /dev/null +++ b/examples/digitalidentity/public/assets/images/icons/document.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/digitalidentity/public/assets/images/icons/email.svg b/examples/digitalidentity/public/assets/images/icons/email.svg new file mode 100644 index 00000000..c4582d6e --- /dev/null +++ b/examples/digitalidentity/public/assets/images/icons/email.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/examples/digitalidentity/public/assets/images/icons/gender.svg b/examples/digitalidentity/public/assets/images/icons/gender.svg new file mode 100644 index 00000000..af5c5772 --- /dev/null +++ b/examples/digitalidentity/public/assets/images/icons/gender.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/digitalidentity/public/assets/images/icons/nationality.svg b/examples/digitalidentity/public/assets/images/icons/nationality.svg new file mode 100644 index 00000000..e57d7522 --- /dev/null +++ b/examples/digitalidentity/public/assets/images/icons/nationality.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/digitalidentity/public/assets/images/icons/phone.svg b/examples/digitalidentity/public/assets/images/icons/phone.svg new file mode 100644 index 00000000..b19cce04 --- /dev/null +++ b/examples/digitalidentity/public/assets/images/icons/phone.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/digitalidentity/public/assets/images/icons/profile.svg b/examples/digitalidentity/public/assets/images/icons/profile.svg new file mode 100644 index 00000000..5c514fc1 --- /dev/null +++ b/examples/digitalidentity/public/assets/images/icons/profile.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/digitalidentity/public/assets/images/icons/verified.svg b/examples/digitalidentity/public/assets/images/icons/verified.svg new file mode 100644 index 00000000..7ca4dbb3 --- /dev/null +++ b/examples/digitalidentity/public/assets/images/icons/verified.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/digitalidentity/public/assets/images/logo.png b/examples/digitalidentity/public/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c60227fabf339e9540e5daac4d2d25e121137752 GIT binary patch literal 2988 zcmV;d3sdxoP)Px=W=TXrRCodHTYGR+)fxZo?%mzIkYEDgl?2c#&jJBdpehy$GAM{5Z>ZDhIR0T< zrqk);pUyZ_ZKvZnw&RQ)|DY&n)lw083ql|vQW+vbK|*+g3C}zrktF-L{eI`>ZtmV} z$nIuCHZwUhH@o-lIp6ut_dDP7+&Csoou;FwC4~f>Nx?-A6G{R-U?kB-(CobEdU9GV zhrICZDzqkf|atnLVsfw< zJE-K})_NScO(0!)+XF^dO5ZkjD>G&LJ>sny*@SF(#9pl*#xss%=(NqTA*$j_Eao=! zxXmv#&57E2G>z7v(@vYe#bG{U27^NJ2mA>5gGlju5R5X!B)@@R0DfNpDJkB_en9$I zo2F^XX;)QMqp7K>1rXuEQTrYA%)x*+RS3%{t8lDD_+2J40>=4bxc9#axP-R95y4^8 z;G6YFz!%#jQG-E&a(8arvK2-9OVp0_ie}88`!viQ+}6|tv9PEZ+ji_!AinoO16D3w zDuDE91qf+XR90f+=B+3@S*}2FIwcGgfLfYp#_ei2>aPKpkHS^H9f1euATWM8^pswa z0SSs^eDxwge+!v-zKM1tV3V@sJ@9=m^U9}yp{_V+N2Rj_!Y3NBe#2+jSF~RV-|cpz z_{#(MRD{M*SS4c-7|=8q4t#YGo40O<-|vUV*5byEs<3hFd!pa;!Fi7q$Lcnhw>@58v8RR6$kxjrnwD3UE<^` zOQC)73j8x)MX>k4a1HTfK<}Mrm?<)^97*%12;M=PZ#o`_q+=W}JerTli)zwWPt7#% z68aex7!(N2$NafZ$p&zssJKM!R%(pP<-o4OLiK#klP!R-|2IdD;-mHdQRNI12>OEi zh7H9F%a@=}AHz(M&h6U`0@`O%@@N5qAW-^k-qm0;7h zZJ>jvro!cg<60S9#cv?^lUD$Dx*<$ah~Uu25F9v8geL%mz~C)S-c7hSzGRpg3R@ho z@A2OQ_Y7zah78d?LRV-exQhy1IIG&wdo{u%Y=_v2;-g2tFrpyY>_+^TqFMX5ZiadKDg?G*LaX{>jT7 zj0xk%;mMiP@#7WltMoiYE2?Zgc z5)cBj{qtQ|y>=aHYiq+aVn|*dRxDl|283cjz&LmQ0xB0nu|HIPgwH8ZUTl7U0R3dwE?cq? zIeoJ&W<>q0{*cb4?y@5v@Ii1JYT;la)=gT6z~CAw^z@8$JUwSNva(uA927GBO1}6CnyR)M%^OXd zLPNeY+fJ1dOFWG=FQ=KQ3%HJem@MI=yR;62!G#hKuQ+v&TQXuxqsicJcWN|f42eyl z@ldO(Mhb6nw3?b)16Ym~5OjB%pc^S2kAwJK7?Kb$jJ3<+ZM)EZA89>w^qHBx)mYm7 z#>OT|7LP^h+U_%!P-<`9G=c|$k%821euqOyBx60`iW@^UGoY={L5Y}vjOhrT|nav){$ z+)~;fD4DyIYr0G8axmyq@^f=VJ0#+2cJ$PiHS0c*y3>xR&fl?xXyDS;9U9$vjBHK)^O&SKf(=h0V6DE3w- zukftjuo0`@U5A^uZW*}=3w=_b@)xX-f}R8hca9(IB)jy!%faC4sS(Umj-Mz~r+aQm z{8&<_GhegzeeBx32S1uJ8DmC`L}u^aAP7A7;Cafa(`QszY$S=|93-y@7W@>!Jjvj@ z5fGF^Qh=ct5#h5$Un}A1DJhSz{zVPpRZ)2v|N3}6)-g<05u5Rnf=kXYLMMQt5s*{3 z;OGSij9W^Q-L56nXV3v#uUMoJ5JC6J%#_l}B8(g{Tt!7@jzU1_*jW$bnLCp^kH_5% zglGekJ8{_lhhBtl^2?H6FmH)vcJ}1}>vgxsCs;GMn<+46!9gj1m^G?sm&Ni*KdE8; zY~^z7lM}=eF+&V-36v%@!{{+|WuT-7bwqDLp4GX}x_a^+1ykoklrN*=lV6~(Ng5%z+jCfe0pylj_%`67Z* zV3Vw0z>y%L%_gim&;M7>?0*|JM?!suSG2;~2yQ&hG*6jgwq`u>I3_64>5LEz9eAvcZJXBC+WsuM8C-H(z6GQ zBtoi{&tMY8Y}Vn3J(}u_buqFs_^}oI=rnm$VSC&eJS3bZRe=24TG$hYjeCpsfs($l-MB=CRu-jS!@!@-~c0000Px~qe(O+IO{fBm{`nU}JlXu>r@}X6&&uGc~CxPbCji z$vpTa?|IK#UQ)@!B$Jv{C85e=Yz78q@G=-1n?VQxS`b>%u5N9ued)gW{v#w>;-1?r z^`h;ZsxDpK( ze1&Nh73GJsM%##D5|^J*2N9-#Cx&!>2N0;tic%iTsH3j!SPCd$?ATYY25v?H1q?Il zsB1fx0ty&A_SLI_n^8ak!;CuW+K#1w0>+Mg^=jZ|6i~o0qmH_^V=173v14Do8n_t+ z6fn%Fqps~(3MgRg*jKLxZbkv~kPx#c*LZDcWSAzWCTU_~ns%3$AzsB#=)@Z9H@f|C zcMlB=4$|JL-4q`ehbRE^FaV=E{GFSgnW6EC3F_|d6#;}jJ?61e)`m|>@4HKSYAJcs z%ZL&(iLBB7nhE&2#obL>&mGdx#-=JMMWkm|lE?9aVYe(3{iKa{lGc3H@H(H=QHSU7 zhtlhzybuv!jJe0@!M%EN4G+=m{5;u{5=F#rOf*8U!&F+^I}qjCLOs2GG&wm%$BrH$ z8)BI>EqEsBMOgXp=m>Q_?xK;gQF6Q8G&(XyE|-fYCMQitqzyHbHrY=!+)9@II{-!w z>GA30O#?_$9Hd)XTIF*)lV)KmY!Al7#1l$V=_FSw2DcB}uOJ3mdVzN{nOeg0T zZEd5z{sD42hpD8vka9A!C?*Eru&j@B@LG%cxjAsYY3k^BOt)^=kkd5;jxoNv8`A$R zZg3R-Jt;g{bbB6A@hhZ*bLh4da1eYj__vOtdl5=KJ4V_#z_*Etjt>CM$v+ypcV1jK z?RR2!#6}cWU@$1UAkLis_;*5#@_H9jSY5tyg>Q6|+1W)l|K>hniYi z=-TIB(eUsv&CJd$RU5j$ys~V1m@E&@6UAo`6+BP+{-2UJ&W?TtFaCwDqdTLfumEFt zbd*}#+NtSbGr7mcu-x-0D3-`NIW+~*YJh6)+@*M1Jf$Wl2esy-8{*odM=b&vOPpe< zE&j{PO^1d@i5E8BskuW(o;yqlHk)5SG6(AE>7|;QTETf{X6DGN%a^YDwz=kG)T_-- z5P28K(su{z1`JqPyc0(z@ zTlat-cXiVgQ($e>ZFYVmy9Q27+Y6dVmIv-1KWiG-Ba!uV`nwppt>$9ad z0*!(5H8eF--TenNH9ZqPQ22IaeugYVEo5o>jI0CoWSJO%NhVBS^leuiFH;~az`$oY zprV75OifSHC!bstrcM@j)>`J~=Bf7HJ^JFS>%^8Qe}yGF4|A{p`q+m0YHI5s_TBJT zZRoGFXf@>`OUJ1ZM<{Z&IzYPm<4ptCU5UHjro zp%}mUS7#_aB{d++(V){w?XV7A{^Oqju<7M0Wd}BhZ25V)boPzYl%A0$wTQ9xiAA$7 zzrGGJs!vv(;rVEopcA3Ll$VxJD#j5PA4k#AQ8YO*MGX9o&MtC~PrxwXzrWmoHa#kQ z68iOjCjIq)B0ZLu#mcQ}S}d$uS=Hcd!2YuJjqk@dQ22597(YJdL*3`RXP&Grm+|F* zg~+H_tmG+rnaGxgXV>P3(v$PyyD2fMbyJ=T3otlwLV7wAPp1Rb)pWnUUWiouwKgiO zn6s}#>+Pl6kmX-|{wO6TCP4m|6W;|TxCP=`L*qmEDPRVa)VEr$*>t{R90l6v(qxK;eMRdzvgG9i%`0hCC%Fh@#~Bh7M)jlT}4p zLI&x}!_$C+#eZ1GS~^}FIiFv=ra^?$+pZ!>fH5$1ybo`7HBo92aJDw5Bm-KHg&|>! zl&}DU#~vRaPwc>Z?#N-GLyWn{Xko!v-{AXM7a4_aQd4`EN{R}=JsctlgJt=v$9JIs zL<Y1Jj&;P_Zhu+?k!>kzrDHSmH#ff*Uv@JfbKNN^c6$BQ6LjYF*Xhs! zd`SZ+%w3Z0b~<|GkoYw;J*0bZe*Wig{~M-YSj}W?72vRReNWc*>*UQljBt|j09sSG ztd{~|1;$dRD$C1>eF5zqotShtO?3{Q7vtk@y85SU0vK<+afXt>eYlLl^vkmUh0iVu zIbAx5o-`Z<5TvH0(EI1!rX1)y{!-hp)oEbRNwu}L@Yu__8;gqU5Bca4HbQv$K=By1Hp#!08thtnB9HW+8)O0TmzwgyW+Y zEiwijTRx>Drpif!gD%v+d~(GB<0>;FT} zT<81m{g6sw>TN~{$_y-7Mx09)HQfC{c3DoxCM=#UBMC3JmUy6)$pM70ijS zC9)FDI)_Md-R^q@BE}#Zn8^Gta3L z0aGAiqSSJaAday3QC3z$hY#(i`Gbs)8=SX4-HSuA9xhZ@qbjqQRk_xENM8#~%o!vDaAFkoX){ zKA`?vD+R*$hW+#;0hXo=_!de^iYW_nJ1gkxE$r*afhpGEa8Ln$v9YlM=oC*|V{SAG zi`TqNOaqt`;LT4RZ9ibMY$;7Um_Gg~Df)O7eZ#{0l|<)Ubqy0;^$UH@zh z0eRTJaA4manukL9_18DVq8~Gzej&mb zJ@{3UB!omIE6s@BydLT#93?hrWS4v!TG? z4qBlXJa=$EVc#a9bKI(_38dfR9xF`0NlA7n!|%YKP`qe;3i#=#H!{Z}1Hn}uT1u8aX%!@vNHX)HeCwv7rq)L2yGHX~KI&FqW}j7#FFN1w$~ zQU0q+$SJU4I>zdeBtooWAA#^Br>8>aFrS31pt7H$5IO~KY_o1qpd&(;g(+BAzE*b( z{{y>1$KddiNEL1=9(oO1@r(<4Ew;*1z%ZsyaA!B3WBBx+?f8ce9VGY66n+2QEgBga z^|`w8@Z^bC5SHPf^dQ19mTSb^nn89ADaQaHSY(UjCY=oQMEGN*7CYt?3sXR~0><)t z4B^41;TTI}Vsm8)ywzqAuzYV3?ny%&R7eUw&(T`6lZg{mvSt&#Mok`{s{)e2;#Hklhn7i z&>VA{qgbONGYBw&^wIYm3d!KGjCFia+HvlC(ZvL&!OOx2#)B&^a?uP_Yc z=Y_zkFsI1ClGJF4Y1D(2hqrFmiggl^(lh3f=F!{2U?e(ax((0R!)x{SzEY5{*y{Sz%e9^H%Yijc^)Bx$X(9Lutg4S56INXpLok zo1KmJToraMdtpJISHJ)P1;@w*$3S8OGb_)PYoAkHeFH+%XM!ZV{P7tih3i7Ll#743 zD%PJE-l+i;F$q}ry$b+Q9K(=CL7tR}WD?QFSMtX88jd_!vTtMC&WDD6s>1cu`OoS^?z<5G% zj?(3ob37G!9}lC#YtcDf59mcou*8b;e=_w{h$BC zE=q2C`S>x!cO5`l&V{LMTdRS~|V@$`9zmrAy|M6r#?>%a`ddcWxs!P&xhW&;N#Y zSL_NH9PGerZfT|SpIpFp36I3?Pd;6F_7HlINWY0aGZeRYmOP<8qkyrBqP&E4@182U zhkUz_kp|SC1~m9{qcmXt6C>RQOjB`t8p)zd-?G)eGj$D>)x+b@e`>Wb+<- ztOLjjrxo>hWu^MNRYr>A&^iIr^^XoI1OqH z;#9Pa^d#s$j+e*_Gp#C-p@7`{5}EclXDg8B?NFxAy!INMK6w%Wh8rikc*;(br~0Sl z*?o3%(rTObN&&;<#U>{wiJZG1y#KDqZWytptwPaGBF~{;khl6rZH z$MEy^IlWIB0t{aSFj#r!jWjusa29)rb0+qX6mSU6L90h0{wbxH^o$D9E6MWlnMK?3>hwEtorCa=r2GO>B=m92#)I(&R;2e|`5mu3*{WBbneEPc&KQzrP#oK#Dx^S>zHul!z%p`kZwPFWdPyc|}ube|V zX0TshAl566h}CWB;0(LOo_6f8(=&H3?&U3(t8NS^^q6>>|IvSdtj58MB2EjS4M#+; z&YUtSpXQ2RmcP~rMiOs2V6f!CSxml_YJzu;TtSN;hYhC#=HMpi z!`AEqxJM=j9pblC>KmFwCJ{4W=p`pf&pkk1TZmHsEH$qFhLi#d7$GEQVqyZq(9QS7 zV?gK%6d|N~c&geMVKzKtQMD=xgp2|T7$GC2s%Se=KmlVry-hWikWoMZBV?ph6>TR9 zC}3=-x2eVwG72bQgp8D`qU}Th1&r85 zGE%CFwi5+RLqd3=zH4ZhVxptvd%eJ?CnTjg2+SbsWY~Y^|zik&xM6WfW*xV5=0&B&sM1C<^#eAj+fDZ+uUvV?_Z)0iyp0%kz + */ + +define('LARAVEL_START', microtime(true)); + +/* +|-------------------------------------------------------------------------- +| Register The Auto Loader +|-------------------------------------------------------------------------- +| +| Composer provides a convenient, automatically generated class loader for +| our application. We just need to utilize it! We'll simply require it +| into the script here so that we don't have to worry about manual +| loading any of our classes later on. It feels great to relax. +| +*/ + +require __DIR__.'/../vendor/autoload.php'; + +/* +|-------------------------------------------------------------------------- +| Turn On The Lights +|-------------------------------------------------------------------------- +| +| We need to illuminate PHP development, so let us turn on the lights. +| This bootstraps the framework and gets it ready for use, then it +| will load up this application so that we can run it and send +| the responses back to the browser and delight our users. +| +*/ + +$app = require_once __DIR__.'/../bootstrap/app.php'; + +/* +|-------------------------------------------------------------------------- +| Run The Application +|-------------------------------------------------------------------------- +| +| Once we have the application, we can handle the incoming request +| through the kernel, and send the associated response back to +| the client's browser allowing them to enjoy the creative +| and wonderful application we have prepared for them. +| +*/ + +$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); + +$response = $kernel->handle( + $request = Illuminate\Http\Request::capture() +); + +$response->send(); + +$kernel->terminate($request, $response); diff --git a/examples/digitalidentity/public/robots.txt b/examples/digitalidentity/public/robots.txt new file mode 100644 index 00000000..eb053628 --- /dev/null +++ b/examples/digitalidentity/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/examples/digitalidentity/resources/views/identity.blade.php b/examples/digitalidentity/resources/views/identity.blade.php new file mode 100644 index 00000000..de3cd057 --- /dev/null +++ b/examples/digitalidentity/resources/views/identity.blade.php @@ -0,0 +1,89 @@ + + + + + + + {{ $title }} + + + + + +
+
+
+ + Yoti + +
+

Digital Identity Share Example

+ +
+
+
+ +
+ +
+

The Yoti app is free to download and use:

+ +
+ + Download on the App Store + + + + get it on Google Play + +
+
+
+ + + + diff --git a/examples/digitalidentity/resources/views/partial/address.blade.php b/examples/digitalidentity/resources/views/partial/address.blade.php new file mode 100644 index 00000000..8e0465c9 --- /dev/null +++ b/examples/digitalidentity/resources/views/partial/address.blade.php @@ -0,0 +1,8 @@ + + @foreach ($address as $key => $value) + + + + + @endforeach +
{{ $key }}{{ $value }}
\ No newline at end of file diff --git a/examples/digitalidentity/resources/views/partial/ageverification.blade.php b/examples/digitalidentity/resources/views/partial/ageverification.blade.php new file mode 100644 index 00000000..e53e1b31 --- /dev/null +++ b/examples/digitalidentity/resources/views/partial/ageverification.blade.php @@ -0,0 +1,14 @@ + + + + + + + + + + + + + +
Check Type{{ $ageVerification->getCheckType() }}
Age{{ $ageVerification->getAge() }}
Result{{ $ageVerification->getResult() ? 'true' : 'false' }}
\ No newline at end of file diff --git a/examples/digitalidentity/resources/views/partial/attribute.blade.php b/examples/digitalidentity/resources/views/partial/attribute.blade.php new file mode 100644 index 00000000..ecfedecd --- /dev/null +++ b/examples/digitalidentity/resources/views/partial/attribute.blade.php @@ -0,0 +1,13 @@ +@if ($value instanceof Yoti\Profile\Attribute\MultiValue) + @foreach ($value as $multiValue) + @include('partial/attribute', ['value' => $multiValue]) + @endforeach +@elseif ($value instanceof \Yoti\Media\Image) + +@elseif ($value instanceof \Yoti\Profile\Attribute\DocumentDetails) + @include('partial/documentdetails', ['documentDetails' => $value]) +@elseif ($value instanceof \DateTime) { + {{ $value->format('d-m-Y') }} +@else + {{ $value }} +@endif \ No newline at end of file diff --git a/examples/digitalidentity/resources/views/partial/documentdetails.blade.php b/examples/digitalidentity/resources/views/partial/documentdetails.blade.php new file mode 100644 index 00000000..6ad2f91f --- /dev/null +++ b/examples/digitalidentity/resources/views/partial/documentdetails.blade.php @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + +
Type{{ $documentDetails->getType() }}
Issuing Country{{ $documentDetails->getIssuingCountry() }}
Document Number{{ $documentDetails->getDocumentNumber() }}
Expiration Date{{ $documentDetails->getExpirationDate()->format('d-m-Y') }}
\ No newline at end of file diff --git a/examples/digitalidentity/resources/views/partial/report.blade.php b/examples/digitalidentity/resources/views/partial/report.blade.php new file mode 100644 index 00000000..28bdd3f0 --- /dev/null +++ b/examples/digitalidentity/resources/views/partial/report.blade.php @@ -0,0 +1,39 @@ +@foreach ($report as $key => $value) + + + + + + + + @foreach ($value as $name => $result) + @if (is_array($result)) + @foreach ($result as $data => $view) + @if (is_array($view)) + @foreach ($view as $key2 => $value2) + @if (is_array($value2)) + {{json_encode($value2)}} + @else + + + + @endif + @endforeach + @else + + + + @endif + @endforeach + @else + + + + @endif + @endforeach + + +
+

{{ $key }}

+
{{ $key2 }}
{{ $value2 }}
{{ $data }}
{{ $view }}
{{ $name }}
{{ $result }}
+ @endforeach diff --git a/examples/digitalidentity/resources/views/receipt.blade.php b/examples/digitalidentity/resources/views/receipt.blade.php new file mode 100644 index 00000000..1fef5463 --- /dev/null +++ b/examples/digitalidentity/resources/views/receipt.blade.php @@ -0,0 +1,104 @@ + + + + + + Yoti client example + + + + + +
+
+ +
+ Powered by + + + +
+ +
+ @if ($selfie) +
+ Yoti + +
+ @endif + + @if ($fullName) +
+ {{ $fullName->getValue() }} +
+ @endif +
+
+ +
+ + + + +
+
Attribute
+
Value
+
Anchors
+
+ +
+
+
S / V
+
Value
+
Sub type
+
+
+ +
+ @foreach($profileAttributes as $item) + @if ($item['obj']) +
+
+
+ + {{ $item['name'] }} +
+
+
+
+ @switch ($item['name']) + @case ('Age Verification') + @include('partial/ageverification', ['ageVerification' => $item['age_verification']]) + @break + @case ('Structured Postal Address') + @include('partial/address', ['address' => $item['obj']->getValue()]) + @break + @case ('Identity Profile Report') + @include('partial/report', ['report' => $item['obj']->getValue()]) + @break + @default + @include('partial/attribute', ['value' => $item['obj']->getValue()]) + @endswitch +
+
+
+
S / V
+
Value
+
Sub type
+ + @foreach($item['obj']->getAnchors() as $anchor) +
{{ $anchor->getType() }}
+
{{ $anchor->getValue() }}
+
{{ $anchor->getSubType() }}
+ @endforeach + +
+
+ @endif + @endforeach +
+
+
+ + + diff --git a/examples/digitalidentity/routes/api.php b/examples/digitalidentity/routes/api.php new file mode 100644 index 00000000..bcb8b189 --- /dev/null +++ b/examples/digitalidentity/routes/api.php @@ -0,0 +1,19 @@ +get('/user', function (Request $request) { + return $request->user(); +}); diff --git a/examples/digitalidentity/routes/channels.php b/examples/digitalidentity/routes/channels.php new file mode 100644 index 00000000..963b0d21 --- /dev/null +++ b/examples/digitalidentity/routes/channels.php @@ -0,0 +1,18 @@ +id === (int) $id; +}); diff --git a/examples/digitalidentity/routes/console.php b/examples/digitalidentity/routes/console.php new file mode 100644 index 00000000..da55196d --- /dev/null +++ b/examples/digitalidentity/routes/console.php @@ -0,0 +1,19 @@ +comment(Inspiring::quote()); +})->describe('Display an inspiring quote'); diff --git a/examples/digitalidentity/routes/web.php b/examples/digitalidentity/routes/web.php new file mode 100644 index 00000000..c592b91f --- /dev/null +++ b/examples/digitalidentity/routes/web.php @@ -0,0 +1,18 @@ +digitalIdentityService = new DigitalIdentityService($sdkId, $pemFile, $config); + $this->id = $sdkId; } /** @@ -110,4 +111,9 @@ public function fetchShareReceipt(string $receiptId): Identity\Receipt { return $this->digitalIdentityService->fetchShareReceipt($receiptId); } + + public function getSdkID() + { + return $this->id; + } } diff --git a/src/Profile/Util/Attribute/AnchorConverter.php b/src/Profile/Util/Attribute/AnchorConverter.php index 89558202..3f919df9 100644 --- a/src/Profile/Util/Attribute/AnchorConverter.php +++ b/src/Profile/Util/Attribute/AnchorConverter.php @@ -115,8 +115,8 @@ private static function convertCertToX509(string $certificate): \stdClass } }); - $decodedX509Data = Json::decode(Json::encode($X509Data), false); - + //$decodedX509Data = Json::decode(Json::encode($X509Data), false); + $decodedX509Data = Json::decode(Json::encode(Json::convert_from_latin1_to_utf8_recursively($X509Data)), false); // Ensure serial number is cast to string. // @see \phpseclib\Math\BigInteger::__toString() $decodedX509Data diff --git a/src/Util/Json.php b/src/Util/Json.php index cdeeefd9..e014e174 100644 --- a/src/Util/Json.php +++ b/src/Util/Json.php @@ -55,4 +55,22 @@ private static function validate(): void throw new JsonException(json_last_error_msg(), json_last_error()); } } + + public static function convert_from_latin1_to_utf8_recursively($dat) + { + if (is_string($dat)) { + return utf8_encode($dat); + } elseif (is_array($dat)) { + $ret = []; + foreach ($dat as $i => $d) $ret[ $i ] = self::convert_from_latin1_to_utf8_recursively($d); + + return $ret; + } elseif (is_object($dat)) { + foreach ($dat as $i => $d) $dat->$i = self::convert_from_latin1_to_utf8_recursively($d); + + return $dat; + } else { + return $dat; + } + } }