Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 891ce81902 | |||
| b7d7025114 | |||
| 41114446d0 | |||
| 592e02484e | |||
| bd9fde94d1 | |||
| d0b2852de5 | |||
| 2fae99e24b | |||
| b74b563c56 | |||
| 04993ebb27 | |||
| db521ad9a9 | |||
| 6a7474173e | |||
| 9f38429c2a | |||
| 9fd6745125 | |||
| 60376ca0a2 | |||
| 6f1f1032f6 | |||
| c6e98eff4c |
9
.env
9
.env
@@ -42,3 +42,12 @@ REDIS_HOST=redis://redis
|
||||
###> symfony/mailer ###
|
||||
MAILER_DSN=null://null
|
||||
###< symfony/mailer ###
|
||||
|
||||
AUTH_METHOD=form_login
|
||||
|
||||
###> drenso/symfony-oidc-bundle ###
|
||||
OIDC_WELL_KNOWN_URL="https://oidc/.well-known"
|
||||
OIDC_CLIENT_ID="Enter your OIDC client id"
|
||||
OIDC_CLIENT_SECRET="Enter your OIDC client secret"
|
||||
OIDC_BYPASS_FORM_LOGIN=false
|
||||
###< drenso/symfony-oidc-bundle ###
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
{
|
||||
"controllers": {
|
||||
"@spomky-labs/pwa-bundle": {
|
||||
"connection-status": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
},
|
||||
"backgroundsync-form": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
},
|
||||
"sync-broadcast": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
},
|
||||
"prefetch-on-demand": {
|
||||
"enabled": true,
|
||||
"fetch": "eager"
|
||||
}
|
||||
},
|
||||
"@symfony/ux-autocomplete": {
|
||||
"autocomplete": {
|
||||
"enabled": true,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"doctrine/doctrine-migrations-bundle": "^3.4",
|
||||
"doctrine/orm": "^3.3",
|
||||
"dragonmantank/cron-expression": "^3.4",
|
||||
"drenso/symfony-oidc-bundle": "^4.2",
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"league/pipeline": "^1.1",
|
||||
"nesbot/carbon": "^3.9",
|
||||
@@ -26,6 +27,7 @@
|
||||
"php-tmdb/api": "^4.1",
|
||||
"predis/predis": "^2.4",
|
||||
"runtime/frankenphp-symfony": "^0.2.0",
|
||||
"spomky-labs/pwa-bundle": "^1.2",
|
||||
"stof/doctrine-extensions-bundle": "^1.14",
|
||||
"symfony/asset": "7.3.*",
|
||||
"symfony/console": "7.3.*",
|
||||
@@ -36,6 +38,7 @@
|
||||
"symfony/flex": "^2",
|
||||
"symfony/form": "7.3.*",
|
||||
"symfony/framework-bundle": "7.3.*",
|
||||
"symfony/http-client": "7.3.*",
|
||||
"symfony/ldap": "7.3.*",
|
||||
"symfony/mailer": "7.3.*",
|
||||
"symfony/mercure-bundle": "^0.3.9",
|
||||
@@ -54,7 +57,8 @@
|
||||
"symfonycasts/reset-password-bundle": "^1.23",
|
||||
"symfonycasts/tailwind-bundle": "^0.10.0",
|
||||
"twig/extra-bundle": "^2.12|^3.0",
|
||||
"twig/twig": "^2.12|^3.0"
|
||||
"twig/twig": "^2.12|^3.0",
|
||||
"web-token/jwt-library": "^4.0"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
|
||||
710
composer.lock
generated
710
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "f368fdd2e0f36d53131785b857200062",
|
||||
"content-hash": "0f98dada0a01d471cebf4eb1b51b9006",
|
||||
"packages": [
|
||||
{
|
||||
"name": "1tomany/rich-bundle",
|
||||
@@ -167,6 +167,66 @@
|
||||
"abandoned": true,
|
||||
"time": "2022-03-30T09:27:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brick/math",
|
||||
"version": "0.13.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/brick/math.git",
|
||||
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04",
|
||||
"reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.2",
|
||||
"phpunit/phpunit": "^10.1",
|
||||
"vimeo/psalm": "6.8.8"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Brick\\Math\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Arbitrary-precision arithmetic library",
|
||||
"keywords": [
|
||||
"Arbitrary-precision",
|
||||
"BigInteger",
|
||||
"BigRational",
|
||||
"arithmetic",
|
||||
"bigdecimal",
|
||||
"bignum",
|
||||
"bignumber",
|
||||
"brick",
|
||||
"decimal",
|
||||
"integer",
|
||||
"math",
|
||||
"mathematics",
|
||||
"rational"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/brick/math/issues",
|
||||
"source": "https://github.com/brick/math/tree/0.13.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/BenMorel",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-29T13:50:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "carbonphp/carbon-doctrine-types",
|
||||
"version": "2.1.0",
|
||||
@@ -1883,6 +1943,93 @@
|
||||
],
|
||||
"time": "2024-10-09T13:47:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "drenso/symfony-oidc-bundle",
|
||||
"version": "v4.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Drenso/symfony-oidc.git",
|
||||
"reference": "6da6a17e206487646799489a1c1dce18ed2f10eb"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Drenso/symfony-oidc/zipball/6da6a17e206487646799489a1c1dce18ed2f10eb",
|
||||
"reference": "6da6a17e206487646799489a1c1dce18ed2f10eb",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-filter": "*",
|
||||
"ext-hash": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"lcobucci/jwt": "^5.0",
|
||||
"php": ">=8.1",
|
||||
"phpseclib/phpseclib": "^3.0.36",
|
||||
"psr/clock": "^1.0",
|
||||
"psr/container": "^1.1 || ^2.0",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0",
|
||||
"symfony/config": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/dependency-injection": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/event-dispatcher": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/http-foundation": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/http-kernel": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/property-access": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/security-bundle": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/security-core": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/security-http": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/string": "^5.4 || ^6.3 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "3.75.0",
|
||||
"phpstan/extension-installer": "1.4.3",
|
||||
"phpstan/phpstan": "2.1.17",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0",
|
||||
"rector/rector": "2.0.18",
|
||||
"symfony/cache": "^5.4 || ^6.3 || ^7.0",
|
||||
"symfony/translation-contracts": "^2.0 || ^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/cache": "When installed, IdP information will be automatically cached"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "v3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Drenso\\OidcBundle\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bob van de Vijver",
|
||||
"email": "bob@drenso.nl"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Feijten",
|
||||
"email": "tobias@drenso.nl"
|
||||
}
|
||||
],
|
||||
"description": "OpenID connect bundle for Symfony",
|
||||
"homepage": "https://gitlab.drenso.nl/intern/symfony-oidc",
|
||||
"keywords": [
|
||||
"OpenID Connect",
|
||||
"oidc",
|
||||
"symfony"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Drenso/symfony-oidc/issues",
|
||||
"source": "https://github.com/Drenso/symfony-oidc/tree/v4.2.0"
|
||||
},
|
||||
"time": "2025-06-19T09:43:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "egulias/email-validator",
|
||||
"version": "4.0.4",
|
||||
@@ -2998,6 +3145,123 @@
|
||||
},
|
||||
"time": "2024-02-19T18:29:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/constant_time_encoding",
|
||||
"version": "v3.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/constant_time_encoding.git",
|
||||
"reference": "df1e7fde177501eee2037dd159cf04f5f301a512"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512",
|
||||
"reference": "df1e7fde177501eee2037dd159cf04f5f301a512",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9",
|
||||
"vimeo/psalm": "^4|^5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ParagonIE\\ConstantTime\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com",
|
||||
"role": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Steve 'Sc00bz' Thomas",
|
||||
"email": "steve@tobtu.com",
|
||||
"homepage": "https://www.tobtu.com",
|
||||
"role": "Original Developer"
|
||||
}
|
||||
],
|
||||
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
|
||||
"keywords": [
|
||||
"base16",
|
||||
"base32",
|
||||
"base32_decode",
|
||||
"base32_encode",
|
||||
"base64",
|
||||
"base64_decode",
|
||||
"base64_encode",
|
||||
"bin2hex",
|
||||
"encoding",
|
||||
"hex",
|
||||
"hex2bin",
|
||||
"rfc4648"
|
||||
],
|
||||
"support": {
|
||||
"email": "info@paragonie.com",
|
||||
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
|
||||
"source": "https://github.com/paragonie/constant_time_encoding"
|
||||
},
|
||||
"time": "2024-05-08T12:36:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "v9.99.100",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">= 7"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*|5.*",
|
||||
"vimeo/psalm": "^1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||
},
|
||||
"type": "library",
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||
"keywords": [
|
||||
"csprng",
|
||||
"polyfill",
|
||||
"pseudorandom",
|
||||
"random"
|
||||
],
|
||||
"support": {
|
||||
"email": "info@paragonie.com",
|
||||
"issues": "https://github.com/paragonie/random_compat/issues",
|
||||
"source": "https://github.com/paragonie/random_compat"
|
||||
},
|
||||
"time": "2020-10-15T08:29:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-http/cache-plugin",
|
||||
"version": "2.0.1",
|
||||
@@ -3661,6 +3925,116 @@
|
||||
},
|
||||
"time": "2024-11-09T15:12:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "3.0.46",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
|
||||
"reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"paragonie/constant_time_encoding": "^1|^2|^3",
|
||||
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
|
||||
"php": ">=5.6.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
|
||||
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
|
||||
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
|
||||
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
|
||||
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"phpseclib/bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"phpseclib3\\": "phpseclib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jim Wigginton",
|
||||
"email": "terrafrost@php.net",
|
||||
"role": "Lead Developer"
|
||||
},
|
||||
{
|
||||
"name": "Patrick Monnerat",
|
||||
"email": "pm@datasphere.ch",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Andreas Fischer",
|
||||
"email": "bantu@phpbb.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Hans-Jürgen Petrich",
|
||||
"email": "petrich@tronic-media.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "graham@alt-three.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
|
||||
"homepage": "http://phpseclib.sourceforge.net",
|
||||
"keywords": [
|
||||
"BigInteger",
|
||||
"aes",
|
||||
"asn.1",
|
||||
"asn1",
|
||||
"blowfish",
|
||||
"crypto",
|
||||
"cryptography",
|
||||
"encryption",
|
||||
"rsa",
|
||||
"security",
|
||||
"sftp",
|
||||
"signature",
|
||||
"signing",
|
||||
"ssh",
|
||||
"twofish",
|
||||
"x.509",
|
||||
"x509"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.46"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/terrafrost",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpseclib",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-26T16:29:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpdoc-parser",
|
||||
"version": "2.1.0",
|
||||
@@ -4383,6 +4757,236 @@
|
||||
],
|
||||
"time": "2023-12-12T12:06:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spomky-labs/pki-framework",
|
||||
"version": "1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Spomky-Labs/pki-framework.git",
|
||||
"reference": "eced5b5ce70518b983ff2be486e902bbd15135ae"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/eced5b5ce70518b983ff2be486e902bbd15135ae",
|
||||
"reference": "eced5b5ce70518b983ff2be486e902bbd15135ae",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"brick/math": "^0.10|^0.11|^0.12|^0.13",
|
||||
"ext-mbstring": "*",
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"ekino/phpstan-banned-code": "^1.0|^2.0|^3.0",
|
||||
"ext-gmp": "*",
|
||||
"ext-openssl": "*",
|
||||
"infection/infection": "^0.28|^0.29",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.3",
|
||||
"phpstan/extension-installer": "^1.3|^2.0",
|
||||
"phpstan/phpstan": "^1.8|^2.0",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.0|^2.0",
|
||||
"phpstan/phpstan-phpunit": "^1.1|^2.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.3|^2.0",
|
||||
"phpunit/phpunit": "^10.1|^11.0|^12.0",
|
||||
"rector/rector": "^1.0|^2.0",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"symfony/string": "^6.4|^7.0",
|
||||
"symfony/var-dumper": "^6.4|^7.0",
|
||||
"symplify/easy-coding-standard": "^12.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "For better performance (or GMP)",
|
||||
"ext-gmp": "For better performance (or BCMath)",
|
||||
"ext-openssl": "For OpenSSL based cyphering"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SpomkyLabs\\Pki\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Joni Eskelinen",
|
||||
"email": "jonieske@gmail.com",
|
||||
"role": "Original developer"
|
||||
},
|
||||
{
|
||||
"name": "Florent Morselli",
|
||||
"email": "florent.morselli@spomky-labs.com",
|
||||
"role": "Spomky-Labs PKI Framework developer"
|
||||
}
|
||||
],
|
||||
"description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.",
|
||||
"homepage": "https://github.com/spomky-labs/pki-framework",
|
||||
"keywords": [
|
||||
"DER",
|
||||
"Private Key",
|
||||
"ac",
|
||||
"algorithm identifier",
|
||||
"asn.1",
|
||||
"asn1",
|
||||
"attribute certificate",
|
||||
"certificate",
|
||||
"certification request",
|
||||
"cryptography",
|
||||
"csr",
|
||||
"decrypt",
|
||||
"ec",
|
||||
"encrypt",
|
||||
"pem",
|
||||
"pkcs",
|
||||
"public key",
|
||||
"rsa",
|
||||
"sign",
|
||||
"signature",
|
||||
"verify",
|
||||
"x.509",
|
||||
"x.690",
|
||||
"x509",
|
||||
"x690"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Spomky-Labs/pki-framework/issues",
|
||||
"source": "https://github.com/Spomky-Labs/pki-framework/tree/1.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/Spomky",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/FlorentMorselli",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-13T08:35:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spomky-labs/pwa-bundle",
|
||||
"version": "1.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Spomky-Labs/pwa-bundle.git",
|
||||
"reference": "c24711ea8428d14a01132d9e42cb17d84218a1ee"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Spomky-Labs/pwa-bundle/zipball/c24711ea8428d14a01132d9e42cb17d84218a1ee",
|
||||
"reference": "c24711ea8428d14a01132d9e42cb17d84218a1ee",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2",
|
||||
"phpdocumentor/reflection-docblock": "^5.3",
|
||||
"psr/log": "^1.0|^2.0|^3.0",
|
||||
"symfony/asset": "^6.4|^7.0",
|
||||
"symfony/asset-mapper": "^6.4|^7.0",
|
||||
"symfony/config": "^6.4|^7.0",
|
||||
"symfony/dependency-injection": "^6.4|^7.0",
|
||||
"symfony/deprecation-contracts": "^3.5",
|
||||
"symfony/http-kernel": "^6.4|^7.0",
|
||||
"symfony/property-access": "^6.4|^7.0",
|
||||
"symfony/property-info": "^6.4|^7.0",
|
||||
"symfony/routing": "^6.4|^7.0",
|
||||
"symfony/serializer": "^6.4|^7.0",
|
||||
"twig/twig": "^3.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"dbrekelmans/bdi": "^1.1",
|
||||
"ekino/phpstan-banned-code": "^1.0|^2.0|^3.0",
|
||||
"ergebnis/phpunit-slow-test-detector": "^2.14",
|
||||
"ext-sockets": "*",
|
||||
"infection/infection": "^0.28|^0.29",
|
||||
"matthiasnoback/symfony-config-test": "^5.1",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.4",
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpdoc-parser": "^1.28|^2.0",
|
||||
"phpstan/phpstan": "^1.0|^2.0",
|
||||
"phpstan/phpstan-beberlei-assert": "^1.0|^2.0",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.0|^2.0",
|
||||
"phpstan/phpstan-phpunit": "^1.4|^2.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.0|^2.0",
|
||||
"phpstan/phpstan-symfony": "^1.4|^2.0",
|
||||
"phpunit/phpunit": "^10.1|^11.0",
|
||||
"rector/rector": "^1.0|^2.0",
|
||||
"staabm/phpstan-todo-by": "^0.1.27|^0.2",
|
||||
"struggle-for-php/sfp-phpstan-psr-log": "^0.21.0|^0.22|^0.23",
|
||||
"symfony/filesystem": "^6.4|^7.0",
|
||||
"symfony/framework-bundle": "^6.4|^7.0",
|
||||
"symfony/mime": "^6.4|^7.0",
|
||||
"symfony/monolog-bundle": "^3.10",
|
||||
"symfony/panther": "^2.1",
|
||||
"symfony/phpunit-bridge": "^6.4|^7.0",
|
||||
"symfony/translation": "^7.0",
|
||||
"symfony/yaml": "^6.4|^7.0",
|
||||
"symplify/easy-coding-standard": "^12.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-gd": "Required to generate icons (or Imagick).",
|
||||
"ext-imagick": "Required to generate icons (or GD).",
|
||||
"symfony/filesystem": "For generating and manipulating icons or screenshots",
|
||||
"symfony/mime": "For generating and manipulating icons or screenshots",
|
||||
"symfony/panther": "For generating screenshots directly from your application"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/spomky-labs/pwa-bundle",
|
||||
"name": "spomky-labs/pwa-bundle"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"SpomkyLabs\\PwaBundle\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Florent Morselli",
|
||||
"homepage": "https://github.com/Spomky"
|
||||
},
|
||||
{
|
||||
"name": "All contributors",
|
||||
"homepage": "https://github.com/spomky-labs/pwa-bundle/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Progressive Web App Manifest Generator Bundle for Symfony.",
|
||||
"homepage": "https://github.com/spomky-labs",
|
||||
"keywords": [
|
||||
"bundle",
|
||||
"pwa",
|
||||
"symfony",
|
||||
"symfony-ux"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Spomky-Labs/pwa-bundle/issues",
|
||||
"source": "https://github.com/Spomky-Labs/pwa-bundle/tree/1.2.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.buymeacoffee.com/FlorentMorselli",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Spomky",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/FlorentMorselli",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-16T08:02:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "stof/doctrine-extensions-bundle",
|
||||
"version": "v1.14.0",
|
||||
@@ -6116,16 +6720,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
"version": "v7.3.0",
|
||||
"version": "v7.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-client.git",
|
||||
"reference": "57e4fb86314015a695a750ace358d07a7e37b8a9"
|
||||
"reference": "4403d87a2c16f33345dca93407a8714ee8c05a64"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/57e4fb86314015a695a750ace358d07a7e37b8a9",
|
||||
"reference": "57e4fb86314015a695a750ace358d07a7e37b8a9",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/4403d87a2c16f33345dca93407a8714ee8c05a64",
|
||||
"reference": "4403d87a2c16f33345dca93407a8714ee8c05a64",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6137,6 +6741,7 @@
|
||||
},
|
||||
"conflict": {
|
||||
"amphp/amp": "<2.5",
|
||||
"amphp/socket": "<1.1",
|
||||
"php-http/discovery": "<1.15",
|
||||
"symfony/http-foundation": "<6.4"
|
||||
},
|
||||
@@ -6149,7 +6754,6 @@
|
||||
"require-dev": {
|
||||
"amphp/http-client": "^4.2.1|^5.0",
|
||||
"amphp/http-tunnel": "^1.0|^2.0",
|
||||
"amphp/socket": "^1.1",
|
||||
"guzzlehttp/promises": "^1.4|^2.0",
|
||||
"nyholm/psr7": "^1.0",
|
||||
"php-http/httplug": "^1.0|^2.0",
|
||||
@@ -6191,7 +6795,7 @@
|
||||
"http"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-client/tree/v7.3.0"
|
||||
"source": "https://github.com/symfony/http-client/tree/v7.3.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6207,7 +6811,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-05-02T08:23:16+00:00"
|
||||
"time": "2025-06-28T07:58:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client-contracts",
|
||||
@@ -10514,6 +11118,96 @@
|
||||
],
|
||||
"time": "2025-05-03T07:21:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "web-token/jwt-library",
|
||||
"version": "4.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/web-token/jwt-library.git",
|
||||
"reference": "650108fa2cdd6cbaaead0dc0ab5302e178b23b0a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/web-token/jwt-library/zipball/650108fa2cdd6cbaaead0dc0ab5302e178b23b0a",
|
||||
"reference": "650108fa2cdd6cbaaead0dc0ab5302e178b23b0a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"brick/math": "^0.12 || ^0.13",
|
||||
"ext-json": "*",
|
||||
"php": ">=8.2",
|
||||
"psr/clock": "^1.0",
|
||||
"spomky-labs/pki-framework": "^1.2.1"
|
||||
},
|
||||
"conflict": {
|
||||
"spomky-labs/jose": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "GMP or BCMath is highly recommended to improve the library performance",
|
||||
"ext-gmp": "GMP or BCMath is highly recommended to improve the library performance",
|
||||
"ext-openssl": "For key management (creation, optimization, etc.) and some algorithms (AES, RSA, ECDSA, etc.)",
|
||||
"ext-sodium": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys",
|
||||
"paragonie/sodium_compat": "Sodium is required for OKP key creation, EdDSA signature algorithm and ECDH-ES key encryption with OKP keys",
|
||||
"spomky-labs/aes-key-wrap": "For all Key Wrapping algorithms (AxxxKW, AxxxGCMKW, PBES2-HSxxx+AyyyKW...)",
|
||||
"symfony/console": "Needed to use console commands",
|
||||
"symfony/http-client": "To enable JKU/X5U support."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Jose\\Component\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Florent Morselli",
|
||||
"homepage": "https://github.com/Spomky"
|
||||
},
|
||||
{
|
||||
"name": "All contributors",
|
||||
"homepage": "https://github.com/web-token/jwt-framework/contributors"
|
||||
}
|
||||
],
|
||||
"description": "JWT library",
|
||||
"homepage": "https://github.com/web-token",
|
||||
"keywords": [
|
||||
"JOSE",
|
||||
"JWE",
|
||||
"JWK",
|
||||
"JWKSet",
|
||||
"JWS",
|
||||
"Jot",
|
||||
"RFC7515",
|
||||
"RFC7516",
|
||||
"RFC7517",
|
||||
"RFC7518",
|
||||
"RFC7519",
|
||||
"RFC7520",
|
||||
"bundle",
|
||||
"jwa",
|
||||
"jwt",
|
||||
"symfony"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/web-token/jwt-library/issues",
|
||||
"source": "https://github.com/web-token/jwt-library/tree/4.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/Spomky",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/FlorentMorselli",
|
||||
"type": "patreon"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-12T11:25:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/assert",
|
||||
"version": "1.11.0",
|
||||
|
||||
@@ -21,4 +21,6 @@ return [
|
||||
Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true],
|
||||
Symfony\UX\Autocomplete\AutocompleteBundle::class => ['all' => true],
|
||||
SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true],
|
||||
Drenso\OidcBundle\DrensoOidcBundle::class => ['all' => true],
|
||||
SpomkyLabs\PwaBundle\SpomkyLabsPwaBundle::class => ['all' => true],
|
||||
];
|
||||
|
||||
19
config/packages/drenso_oidc.yaml
Normal file
19
config/packages/drenso_oidc.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
drenso_oidc:
|
||||
#default_client: default # The default client, will be aliased to OidcClientInterface
|
||||
clients:
|
||||
default: # The client name, each client will be aliased to its name (for example, $defaultOidcClient)
|
||||
# Required OIDC client configuration
|
||||
well_known_url: '%env(OIDC_WELL_KNOWN_URL)%'
|
||||
client_id: '%env(OIDC_CLIENT_ID)%'
|
||||
client_secret: '%env(OIDC_CLIENT_SECRET)%'
|
||||
redirect_route: '/login/oidc/auth'
|
||||
|
||||
# Extra configuration options
|
||||
#redirect_route: '/login_check'
|
||||
#custom_client_headers: []
|
||||
|
||||
# Add any extra client
|
||||
#link: # Will be accessible using $linkOidcClient
|
||||
#well_known_url: '%env(LINK_WELL_KNOWN_URL)%'
|
||||
#client_id: '%env(LINK_CLIENT_ID)%'
|
||||
#client_secret: '%env(LINK_CLIENT_SECRET)%'
|
||||
21
config/packages/pwa.yaml
Normal file
21
config/packages/pwa.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
pwa:
|
||||
manifest:
|
||||
enabled: true
|
||||
name: "Torsearch"
|
||||
short_name: "torsearch"
|
||||
start_url: "/"
|
||||
display: "standalone"
|
||||
id: "/"
|
||||
background_color: "#083344"
|
||||
theme_color: "#f98e44"
|
||||
description: Torsearch provides a simple and intuitive way to manage your personal media library.
|
||||
icons:
|
||||
- src: "icon.png"
|
||||
sizes: [ 192 ]
|
||||
- src: "icon.png"
|
||||
sizes: [ 192 ]
|
||||
purpose: maskable
|
||||
categories:
|
||||
- entertainment
|
||||
- multimedia
|
||||
- utilities
|
||||
@@ -10,6 +10,9 @@ security:
|
||||
class: App\User\Framework\Entity\User
|
||||
property: email
|
||||
|
||||
app_oidc:
|
||||
id: App\User\Framework\Security\OidcUserProvider
|
||||
|
||||
app_ldap:
|
||||
id: App\User\Framework\Security\LdapUserProvider
|
||||
|
||||
@@ -18,14 +21,18 @@ security:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
main:
|
||||
lazy: true
|
||||
provider: app_local
|
||||
logout:
|
||||
path: /logout
|
||||
provider: app_oidc
|
||||
form_login:
|
||||
login_path: app_login
|
||||
check_path: app_login
|
||||
enable_csrf: true
|
||||
logout:
|
||||
path: app_logout
|
||||
oidc:
|
||||
login_path: '/login/oidc'
|
||||
check_path: '/login/oidc/auth'
|
||||
enable_end_session_listener: true
|
||||
entry_point: form_login
|
||||
|
||||
# activate different ways to authenticate
|
||||
# https://symfony.com/doc/current/security.html#the-firewall
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
security:
|
||||
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
|
||||
password_hashers:
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
|
||||
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
|
||||
providers:
|
||||
users_in_memory: { memory: null }
|
||||
app_local:
|
||||
entity:
|
||||
class: App\User\Framework\Entity\User
|
||||
property: email
|
||||
|
||||
app_ldap:
|
||||
id: App\User\Framework\Security\LdapUserProvider
|
||||
|
||||
firewalls:
|
||||
dev:
|
||||
pattern: ^/(_(profiler|wdt)|css|images|js)/
|
||||
security: false
|
||||
main:
|
||||
lazy: true
|
||||
provider: app_ldap
|
||||
entry_point: form_login_ldap
|
||||
form_login_ldap:
|
||||
login_path: app_login
|
||||
check_path: app_login
|
||||
enable_csrf: true
|
||||
service: Symfony\Component\Ldap\Ldap
|
||||
dn_string: '%env(LDAP_DN_STRING)%'
|
||||
form_login:
|
||||
login_path: app_login
|
||||
check_path: app_login
|
||||
enable_csrf: true
|
||||
logout:
|
||||
path: app_logout
|
||||
|
||||
# activate different ways to authenticate
|
||||
# https://symfony.com/doc/current/security.html#the-firewall
|
||||
|
||||
# https://symfony.com/doc/current/security/impersonating_user.html
|
||||
# switch_user: true
|
||||
|
||||
# Easy way to control access for large sections of your site
|
||||
# Note: Only the *first* access control that matches will be used
|
||||
access_control:
|
||||
- { path: ^/reset-password, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/login, roles: PUBLIC_ACCESS }
|
||||
- { path: ^/, roles: ROLE_USER } # Or ROLE_ADMIN, ROLE_SUPER_ADMIN,
|
||||
|
||||
when@test:
|
||||
security:
|
||||
password_hashers:
|
||||
# By default, password hashers are resource intensive and take time. This is
|
||||
# important to generate secure password hashes. In tests however, secure hashes
|
||||
# are not important, waste resources and increase test times. The following
|
||||
# reduces the work factor to the lowest possible values.
|
||||
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
|
||||
algorithm: auto
|
||||
cost: 4 # Lowest possible value for bcrypt
|
||||
time_cost: 3 # Lowest possible value for argon
|
||||
memory_cost: 10 # Lowest possible value for argon
|
||||
@@ -6,6 +6,7 @@
|
||||
parameters:
|
||||
# App
|
||||
app.url: '%env(APP_URL)%'
|
||||
app.version: '%env(default:app.default.version:APP_VERSION)%'
|
||||
|
||||
# Debrid Services
|
||||
app.debrid.real_debrid.key: '%env(REAL_DEBRID_KEY)%'
|
||||
@@ -34,7 +35,14 @@ parameters:
|
||||
app.default.version: '0.dev'
|
||||
app.default.timezone: 'America/Chicago'
|
||||
|
||||
app.version: '%env(default:app.default.version:APP_VERSION)%'
|
||||
# Auth
|
||||
auth.default.method: 'form_login'
|
||||
auth.method: '%env(default:auth.default.method:AUTH_METHOD)%'
|
||||
|
||||
auth.oidc.well_known_url: '%env(OIDC_WELL_KNOWN_URL)%'
|
||||
auth.oidc.client_id: '%env(OIDC_CLIENT_ID)%'
|
||||
auth.oidc.client_secret: '%env(OIDC_CLIENT_SECRET)%'
|
||||
auth.oidc.bypass_form_login: '%env(bool:OIDC_BYPASS_FORM_LOGIN)%'
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
|
||||
@@ -7,6 +7,11 @@ APP_URL="https://dev.caldwell.digital"
|
||||
APP_SECRET="70169beadfbc8101c393cbfbba27a313"
|
||||
APP_ENV=prod
|
||||
|
||||
# Mercure is a Caddy module built into the webserver
|
||||
# that facilitates the usage of websockets to transmit
|
||||
# real time data (download progress, etc.)
|
||||
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
||||
|
||||
# Use the DATABASE_URL below to use the MariaDB container
|
||||
# provided in the example.compose.yml file, or remove this
|
||||
# line and fill in the details of your own MySQL/MariaDB server
|
||||
@@ -19,39 +24,48 @@ DATABASE_URL="mysql://root:password@database:3306/app?serverVersion=10.6.19.2-Ma
|
||||
# This key is never saved anywhere
|
||||
# else and is passed to Torrentio
|
||||
# to retrieve download options
|
||||
#REAL_DEBRID_KEY=""
|
||||
REAL_DEBRID_KEY=""
|
||||
|
||||
# Enter you TMDB API key
|
||||
# Enter your TMDB API key
|
||||
# This is used to provide rich search results
|
||||
# when searching for media and rendering the
|
||||
# Popular Movies and TV Shows section.
|
||||
#TMDB_API=
|
||||
|
||||
REAL_DEBRID_KEY=""
|
||||
TMDB_API=eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI0ZTJjYjJhOGUzOGJhNjdiNjVhOGU1NGM0ZWI1MzhmOCIsIm5iZiI6MTczNzkyNjA0NC41NjQsInN1YiI6IjY3OTZhNTljYzdiMDFiNzJjNzIzZWM5YiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.e8DbNe9qrSBC1y-ANRv-VWBAtls-ZS2r7aNCiI68mpw
|
||||
|
||||
|
||||
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
||||
TMDB_API=""
|
||||
|
||||
# Use your own Redis instance or use the
|
||||
# below value to use the container included
|
||||
# in the example compose.yml file.
|
||||
REDIS_HOST="redis://redis"
|
||||
|
||||
### Auth ###
|
||||
# Change to "oidc" to and provide the required
|
||||
# environment variables below to use OIDC auth.
|
||||
AUTH_METHOD=form_login
|
||||
|
||||
# OIDC
|
||||
OIDC_WELL_KNOWN_URL=
|
||||
OIDC_CLIENT_ID=
|
||||
OIDC_CLIENT_SECRET=
|
||||
# Allows you to skip the login page and directly
|
||||
# rely on your IdP for auth.
|
||||
OIDC_BYPASS_FORM_LOGIN=
|
||||
|
||||
|
||||
# LDAP Config: To use LDAP, enter the below fields
|
||||
# and run 'php bin/console config:set auth.method ldap'
|
||||
LDAP_HOST=
|
||||
LDAP_PORT=
|
||||
LDAP_ENCRYPTION=
|
||||
LDAP_BASE_DN=
|
||||
LDAP_BIND_USER=
|
||||
LDAP_BIND_PASS=
|
||||
LDAP_DN_STRING=
|
||||
LDAP_UID_KEY="uid"
|
||||
# (LDAP is still in progress and not ready for use)
|
||||
#LDAP_HOST=
|
||||
#LDAP_PORT=
|
||||
#LDAP_ENCRYPTION=
|
||||
#LDAP_BASE_DN=
|
||||
#LDAP_BIND_USER=
|
||||
#LDAP_BIND_PASS=
|
||||
#LDAP_DN_STRING=
|
||||
#LDAP_UID_KEY="uid"
|
||||
# LDAP group that identifies an Admin
|
||||
# Users with this LDAP group will automatically
|
||||
# get the admin role in this system.
|
||||
LDAP_ADMIN_ROLE_DN=""
|
||||
LDAP_EMAIL_ATTRIBUTE=mail
|
||||
LDAP_USERNAME_ATTRIBUTE=uid
|
||||
LDAP_NAME_ATTRIBUTE=displayname
|
||||
#LDAP_ADMIN_ROLE_DN=""
|
||||
#LDAP_EMAIL_ATTRIBUTE=mail
|
||||
#LDAP_USERNAME_ATTRIBUTE=uid
|
||||
#LDAP_NAME_ATTRIBUTE=displayname
|
||||
|
||||
BIN
public/icon.png
Normal file
BIN
public/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
@@ -23,6 +23,21 @@ final class ConfigResolver
|
||||
|
||||
#[Autowire(param: 'media.tvshows.path')]
|
||||
private readonly ?string $tvshowsPath = null,
|
||||
|
||||
#[Autowire(param: 'auth.method')]
|
||||
private readonly ?string $authMethod = null,
|
||||
|
||||
#[Autowire(param: 'auth.oidc.well_known_url')]
|
||||
private readonly ?string $authOidcWellKnownUrl = null,
|
||||
|
||||
#[Autowire(param: 'auth.oidc.client_id')]
|
||||
private readonly ?string $authOidcClientId = null,
|
||||
|
||||
#[Autowire(param: 'auth.oidc.client_secret')]
|
||||
private readonly ?string $authOidcClientSecret = null,
|
||||
|
||||
#[Autowire(param: 'auth.oidc.bypass_form_login')]
|
||||
private ?bool $authOidcBypassFormLogin = null,
|
||||
) {}
|
||||
|
||||
public function validate(): bool
|
||||
@@ -46,4 +61,35 @@ final class ConfigResolver
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
public function authIs(string $method): bool
|
||||
{
|
||||
if (strtolower($method) === strtolower($this->getAuthMethod())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getAuthMethod(): string
|
||||
{
|
||||
return strtolower($this->authMethod);
|
||||
}
|
||||
|
||||
public function bypassFormLogin(): bool
|
||||
{
|
||||
return $this->authOidcBypassFormLogin;
|
||||
}
|
||||
|
||||
public function getAuthConfig(): array
|
||||
{
|
||||
return [
|
||||
'method' => $this->authMethod,
|
||||
'oidc' => [
|
||||
'well_known_url' => $this->authOidcWellKnownUrl,
|
||||
'client_id' => $this->authOidcClientId,
|
||||
'client_secret' => $this->authOidcClientSecret,
|
||||
'bypass_form_login' => $this->authOidcBypassFormLogin,
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'config:set',
|
||||
description: 'Add a short description for your command',
|
||||
description: '[deprecated] This command currently serves no use. It may be re-purposed or removed in the future.',
|
||||
)]
|
||||
class ConfigSetCommand extends Command
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'db:seed',
|
||||
description: 'Seed the database with required data.',
|
||||
description: 'Seeds the database with required data. This command is run every time a new container is created from the torsearch-app image and is part of the init process.',
|
||||
)]
|
||||
class SeedDatabaseCommand extends Command
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'startup:status',
|
||||
description: 'Add a short description for your command',
|
||||
description: 'Used by the Docker healthcheck system to signal when the container is healthy.',
|
||||
)]
|
||||
class StartupStatusCommand extends Command
|
||||
{
|
||||
|
||||
112
src/Base/Framework/Command/UserResetPasswordCommand.php
Normal file
112
src/Base/Framework/Command/UserResetPasswordCommand.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Base\Framework\Command;
|
||||
|
||||
use App\User\Framework\Repository\UserRepository;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
#[AsCommand(name: 'user:reset-password', description: 'Resets the password for the given user. Requires either the ID or email of the User. You will be asked for the password after running the command.')]
|
||||
class UserResetPasswordCommand extends Command
|
||||
{
|
||||
private readonly Security $security;
|
||||
private readonly UserRepository $userRepository;
|
||||
private readonly UserPasswordHasherInterface $hasher;
|
||||
|
||||
public function __construct(
|
||||
Security $security,
|
||||
UserRepository $userRepository,
|
||||
UserPasswordHasherInterface $hasher,
|
||||
) {
|
||||
parent::__construct();
|
||||
$this->security = $security;
|
||||
$this->userRepository = $userRepository;
|
||||
$this->hasher = $hasher;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addOption('id', null, InputOption::VALUE_REQUIRED, 'The ID of the user in the database.')
|
||||
->addOption('email', null, InputOption::VALUE_REQUIRED, 'The email of the user.')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$queryParams = $this->parseInput($input, $io);
|
||||
if ([] === $queryParams) {
|
||||
$io->error('No ID or Email specified. Please run again and pass the "--id" or "--email" option.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$user = $this->userRepository->findOneBy($queryParams);
|
||||
if (null === $user) {
|
||||
$io->error('No such user exists.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
try {
|
||||
$newPassword = $this->askForPassword($input, $output);
|
||||
$this->updateUsersPassword($user, $newPassword);
|
||||
} catch (\Throwable $exception) {
|
||||
$io->error($exception->getMessage());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->success('Success. The password has been reset.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function parseInput(InputInterface $input, SymfonyStyle $io): array
|
||||
{
|
||||
if ($input->getOption('id')) {
|
||||
return ['id' => $input->getOption('id')];
|
||||
} elseif ($input->getOption('email')) {
|
||||
return ['email' => $input->getOption('email')];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function askForPassword(InputInterface $input, OutputInterface $output): ?string
|
||||
{
|
||||
$questionHelper = new QuestionHelper();
|
||||
$question = new Question('New password (input is hidden): ')
|
||||
->setHidden(true)
|
||||
->setHiddenFallback(false)
|
||||
->setNormalizer(function (?string $value): string {
|
||||
return $value ?? '';
|
||||
})
|
||||
->setValidator(function (string $value): string {
|
||||
if ('' === trim($value)) {
|
||||
throw new \Exception('The password cannot be empty');
|
||||
}
|
||||
return $value;
|
||||
})
|
||||
->setMaxAttempts(5)
|
||||
;
|
||||
|
||||
return $questionHelper->ask($input, $output, $question);
|
||||
}
|
||||
|
||||
private function updateUsersPassword(UserInterface $user, string $newPassword): void
|
||||
{
|
||||
$user->setPassword(
|
||||
$this->hasher->hashPassword($user, $newPassword)
|
||||
);
|
||||
$this->userRepository->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Base\Framework\Controller;
|
||||
|
||||
use App\Base\Util\Broadcaster;
|
||||
use App\Base\Service\Broadcaster;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Base\Util;
|
||||
namespace App\Base\Service;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
namespace App\Download\Framework\Controller;
|
||||
|
||||
use App\Base\Util\Broadcaster;
|
||||
use App\Base\Service\Broadcaster;
|
||||
use App\Download\Action\Handler\DeleteDownloadHandler;
|
||||
use App\Download\Action\Handler\DownloadSeasonHandler;
|
||||
use App\Download\Action\Handler\PauseDownloadHandler;
|
||||
use App\Download\Action\Handler\ResumeDownloadHandler;
|
||||
use App\Download\Action\Input\DeleteDownloadInput;
|
||||
@@ -13,8 +12,6 @@ use App\Download\Action\Input\DownloadSeasonInput;
|
||||
use App\Download\Action\Input\PauseDownloadInput;
|
||||
use App\Download\Action\Input\ResumeDownloadInput;
|
||||
use App\Download\Framework\Repository\DownloadRepository;
|
||||
use App\User\Dto\UserPreferencesFactory;
|
||||
use Nihilarr\PTN;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Monitor\Framework\Controller;
|
||||
|
||||
use App\Base\Util\Broadcaster;
|
||||
use App\Base\Service\Broadcaster;
|
||||
use App\Monitor\Action\Handler\AddMonitorHandler;
|
||||
use App\Monitor\Action\Handler\DeleteMonitorHandler;
|
||||
use App\Monitor\Action\Input\AddMonitorInput;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Torrentio\Framework\Controller;
|
||||
|
||||
use App\Base\Util\Broadcaster;
|
||||
use App\Base\Service\Broadcaster;
|
||||
use App\Torrentio\Action\Handler\GetMovieOptionsHandler;
|
||||
use App\Torrentio\Action\Handler\GetTvShowOptionsHandler;
|
||||
use App\Torrentio\Action\Input\GetMovieOptionsInput;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Torrentio\Result;
|
||||
|
||||
use App\Base\Util\CountryLanguages;
|
||||
use App\User\Database\CountryLanguages;
|
||||
use Nihilarr\PTN;
|
||||
|
||||
class ResultFactory
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace App\Twig\Components;
|
||||
|
||||
use Aimeos\Map;
|
||||
use App\Base\Util\QualityList;
|
||||
use App\User\Database\QualityList;
|
||||
use App\User\Framework\Repository\PreferencesRepository;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Base\Util;
|
||||
namespace App\User\Database;
|
||||
|
||||
class CountryCodes
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Base\Util;
|
||||
namespace App\User\Database;
|
||||
|
||||
class CountryLanguages
|
||||
{
|
||||
@@ -137,4 +137,13 @@ class CountryLanguages
|
||||
|
||||
return $countryLanguages[$countryName] ?? null;
|
||||
}
|
||||
|
||||
public static function asSelectOptions(): array
|
||||
{
|
||||
$result = [];
|
||||
foreach (static::$languages as $language) {
|
||||
$result[$language] = $language;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Base\Util;
|
||||
namespace App\User\Database;
|
||||
|
||||
class ProviderList
|
||||
{
|
||||
@@ -23,4 +23,13 @@ class ProviderList
|
||||
{
|
||||
return self::$providers;
|
||||
}
|
||||
|
||||
public static function asSelectOptions(): array
|
||||
{
|
||||
$result = [];
|
||||
foreach (static::$providers as $provider) {
|
||||
$result[$provider] = $provider;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Base\Util;
|
||||
namespace App\User\Database;
|
||||
|
||||
class QualityList
|
||||
{
|
||||
@@ -100,6 +100,15 @@ class QualityList
|
||||
return array_search($key, self::$qualities) ?? null;
|
||||
}
|
||||
|
||||
public static function asSelectOptions(): array
|
||||
{
|
||||
$result = [];
|
||||
foreach (array_keys(static::$qualities) as $quality) {
|
||||
$result[$quality] = $quality;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getAsReverseMap(): array
|
||||
{
|
||||
$results = [];
|
||||
@@ -2,22 +2,30 @@
|
||||
|
||||
namespace App\User\Framework\Controller\Web;
|
||||
|
||||
use App\Base\ConfigResolver;
|
||||
use App\User\Framework\Repository\UserRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
|
||||
class LoginController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/login', name: 'app_login')]
|
||||
public function login(AuthenticationUtils $authenticationUtils, UserRepository $userRepository): Response
|
||||
public function login(ConfigResolver $config, AuthenticationUtils $authenticationUtils, UserRepository $userRepository): Response
|
||||
{
|
||||
if ((new ArrayCollection($userRepository->findAll()))->count() === 0) {
|
||||
return $this->redirectToRoute('app_getting_started');
|
||||
}
|
||||
|
||||
if ($config->authIs('oidc') && $config->bypassFormLogin()) {
|
||||
return $this->redirectToRoute('app_login_oidc');
|
||||
}
|
||||
|
||||
// get the login error if there is one
|
||||
$error = $authenticationUtils->getLastAuthenticationError();
|
||||
|
||||
@@ -25,13 +33,14 @@ class LoginController extends AbstractController
|
||||
$lastUsername = $authenticationUtils->getLastUsername();
|
||||
|
||||
return $this->render('user/login.html.twig', [
|
||||
'show_oidc_button' => $config->authIs('oidc'),
|
||||
'last_username' => $lastUsername,
|
||||
'error' => $error,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/logout', name: 'app_logout')]
|
||||
public function logout(): void
|
||||
public function logout(Security $security, Request $request): void
|
||||
{
|
||||
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
|
||||
}
|
||||
|
||||
46
src/User/Framework/Controller/Web/LoginOidcController.php
Normal file
46
src/User/Framework/Controller/Web/LoginOidcController.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\User\Framework\Controller\Web;
|
||||
|
||||
use App\Base\ConfigResolver;
|
||||
use Drenso\OidcBundle\OidcClientInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class LoginOidcController extends AbstractController
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
private ConfigResolver $configResolver,
|
||||
) {}
|
||||
|
||||
#[Route('/login/oidc', name: 'app_login_oidc')]
|
||||
public function oidcStart(OidcClientInterface $oidcClient): RedirectResponse
|
||||
{
|
||||
if (false === $this->configResolver->authIs('oidc')) {
|
||||
throw new \Exception('You must configure the OIDC environment variables before logging in at this route.');
|
||||
}
|
||||
|
||||
// Redirect to authorization @ OIDC provider
|
||||
return $oidcClient->generateAuthorizationRedirect(scopes: ['openid', 'profile']);
|
||||
}
|
||||
|
||||
#[Route('/login/oidc/auth', name: 'app_login_oidc_auth')]
|
||||
public function oidcAuthenticate(): RedirectResponse
|
||||
{
|
||||
if (false === $this->configResolver->authIs('oidc')) {
|
||||
throw new \Exception('You must configure the OIDC environment variables before logging in at this route.');
|
||||
}
|
||||
|
||||
throw new \LogicException('This method can be blank - it will be intercepted by the "oidc" key on your firewall.');
|
||||
}
|
||||
|
||||
#[Route('/logout/oidc', 'app_logout_oidc')]
|
||||
public function oidcLogout(OidcClientInterface $oidcClient, Request $request, Security $security): RedirectResponse
|
||||
{
|
||||
// ToDo: Configure multiple authentication methods and redirect to the form login here
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\User\Framework\Controller\Web;
|
||||
|
||||
use App\Base\Util\Broadcaster;
|
||||
use App\Base\Util\CountryLanguages;
|
||||
use App\Base\Util\ProviderList;
|
||||
use App\Base\Util\QualityList;
|
||||
use App\Base\Service\Broadcaster;
|
||||
use App\User\Action\Handler\SaveUserDownloadPreferencesHandler;
|
||||
use App\User\Action\Handler\SaveUserMediaPreferencesHandler;
|
||||
use App\User\Action\Input\SaveUserDownloadPreferencesInput;
|
||||
use App\User\Action\Input\SaveUserMediaPreferencesInput;
|
||||
use App\User\Database\CountryLanguages;
|
||||
use App\User\Database\ProviderList;
|
||||
use App\User\Database\QualityList;
|
||||
use App\User\Dto\UserPreferencesFactory;
|
||||
use App\User\Framework\Form\GettingStartedFilterForm;
|
||||
use App\User\Framework\Repository\PreferencesRepository;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -41,6 +43,7 @@ class PreferencesController extends AbstractController
|
||||
'qualities' => QualityList::getBaseQualities(),
|
||||
'mediaPreferences' => $mediaPreferences,
|
||||
'downloadPreferences' => $downloadPreferences,
|
||||
'filterForm' => $this->createForm(GettingStartedFilterForm::class, (array) UserPreferencesFactory::createFromUser($this->getUser())),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -72,6 +75,7 @@ class PreferencesController extends AbstractController
|
||||
'qualities' => QualityList::getBaseQualities(),
|
||||
'mediaPreferences' => $mediaPreferences,
|
||||
'downloadPreferences' => $downloadPreferences,
|
||||
'filterForm' => $this->createForm(GettingStartedFilterForm::class ?? null),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,30 +5,22 @@ namespace App\User\Framework\Controller\Web;
|
||||
use App\User\Action\Command\RegisterUserCommand;
|
||||
use App\User\Action\Handler\RegisterUserHandler;
|
||||
use App\User\Framework\Entity\User;
|
||||
use App\User\Framework\Form\GettingStartedFilterForm;
|
||||
use App\User\Framework\Form\RegistrationFormType;
|
||||
use App\User\Framework\Pipeline\GettingStarted\AddPreferencesToDatabase;
|
||||
use App\User\Framework\Pipeline\GettingStarted\GettingStartedInput;
|
||||
use App\User\Framework\Pipeline\GettingStarted\MigrateDatabase;
|
||||
use App\User\Framework\Repository\PreferencesRepository;
|
||||
use App\User\Framework\Repository\UserRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use League\Pipeline\Pipeline;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class RegistrationController extends AbstractController
|
||||
{
|
||||
public function __construct(private readonly RegisterUserHandler $registerUserHandler,
|
||||
private readonly RequestStack $requestStack
|
||||
)
|
||||
{
|
||||
}
|
||||
private readonly RequestStack $requestStack,
|
||||
) {}
|
||||
|
||||
#[Route('/register', name: 'app_register')]
|
||||
public function register(
|
||||
@@ -57,7 +49,7 @@ class RegistrationController extends AbstractController
|
||||
}
|
||||
|
||||
#[Route(path: '/getting-started', name: 'app_getting_started')]
|
||||
public function gettingStarted(Request $request, Security $security, UserRepository $userRepository, PreferencesRepository $preferencesRepository, KernelInterface $kernel, LoggerInterface $logger): Response
|
||||
public function gettingStarted(Request $request, Security $security, UserRepository $userRepository): Response
|
||||
{
|
||||
if ((new ArrayCollection($userRepository->findAll()))->count() !== 0) {
|
||||
return $this->redirectToRoute('app_index');
|
||||
@@ -73,14 +65,42 @@ class RegistrationController extends AbstractController
|
||||
password: $form->get('plainPassword')->getData(),
|
||||
));
|
||||
|
||||
$security->login($user->user);
|
||||
$security->login($user->user, 'form_login');
|
||||
$this->requestStack->getCurrentRequest()->getSession()->set('mercure_alert_topic', 'alerts_' . uniqid());
|
||||
|
||||
return $this->redirectToRoute('app_index');
|
||||
return $this->redirectToRoute('app_getting_started_filter');
|
||||
}
|
||||
|
||||
return $this->render('user/getting-started.html.twig', [
|
||||
return $this->render('user/getting_started/register-user.html.twig', [
|
||||
'registrationForm' => $form,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/getting-started/filter', name: 'app_getting_started_filter')]
|
||||
public function gettingStartedPreferences(Request $request, UserRepository $userRepository): Response
|
||||
{
|
||||
if ((new ArrayCollection($userRepository->findAll()))->count() !== 0) {
|
||||
return $this->redirectToRoute('app_index');
|
||||
}
|
||||
|
||||
$form = $this->createForm(GettingStartedFilterForm::class);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
foreach ($form->getData() as $preference => $value) {
|
||||
if (null !== $value) {
|
||||
$this->getUser()->updateUserPreference($preference, $value);
|
||||
}
|
||||
}
|
||||
$userRepository->getEntityManager()->flush();
|
||||
return $this->redirectToRoute('app_index');
|
||||
}
|
||||
|
||||
return $this->render(
|
||||
'user/getting_started/filter.html.twig',
|
||||
[
|
||||
'form' => $form,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
|
||||
namespace App\User\Framework\Controller\Web;
|
||||
|
||||
use App\Base\ConfigResolver;
|
||||
use App\User\Framework\Entity\User;
|
||||
use App\User\Framework\Form\ChangePasswordForm;
|
||||
use App\User\Framework\Form\ResetPasswordRequestForm;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
|
||||
use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
|
||||
@@ -28,7 +29,9 @@ class ResetPasswordController extends AbstractController
|
||||
|
||||
public function __construct(
|
||||
private ResetPasswordHelperInterface $resetPasswordHelper,
|
||||
private EntityManagerInterface $entityManager
|
||||
private EntityManagerInterface $entityManager,
|
||||
private readonly ConfigResolver $configResolver,
|
||||
private readonly Security $security
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -36,17 +39,25 @@ class ResetPasswordController extends AbstractController
|
||||
* Display & process form to request a password reset.
|
||||
*/
|
||||
#[Route('', name: 'app_forgot_password_request')]
|
||||
public function request(Request $request, MailerInterface $mailer, TranslatorInterface $translator): Response
|
||||
{
|
||||
public function request(
|
||||
Request $request,
|
||||
MailerInterface $mailer,
|
||||
LoggerInterface $logger
|
||||
): Response {
|
||||
$form = $this->createForm(ResetPasswordRequestForm::class);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($this->configResolver->authIs('oidc')) {
|
||||
$this->addFlash('reset_password_error', 'Your auth method is set to "oidc", so you will need to reset your password with your identity provider.');
|
||||
return $this->render('user/reset_password/request.html.twig', [
|
||||
'requestForm' => $form,
|
||||
])->setStatusCode(Response::HTTP_ACCEPTED);
|
||||
}
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var string $email */
|
||||
$email = $form->get('email')->getData();
|
||||
|
||||
return $this->processSendingPasswordResetEmail($email, $mailer, $translator
|
||||
);
|
||||
return $this->processSendingPasswordResetEmail($email, $mailer, $logger);
|
||||
}
|
||||
|
||||
return $this->render('user/reset_password/request.html.twig', [
|
||||
@@ -75,8 +86,12 @@ class ResetPasswordController extends AbstractController
|
||||
* Validates and process the reset URL that the user clicked in their email.
|
||||
*/
|
||||
#[Route('/reset/{token}', name: 'app_reset_password')]
|
||||
public function reset(Request $request, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator, ?string $token = null): Response
|
||||
{
|
||||
public function reset(
|
||||
Request $request,
|
||||
UserPasswordHasherInterface $passwordHasher,
|
||||
TranslatorInterface $translator,
|
||||
?string $token = null
|
||||
): Response {
|
||||
if ($token) {
|
||||
// We store the token in session and remove it from the URL, to avoid the URL being
|
||||
// loaded in a browser and potentially leaking the token to 3rd party JavaScript.
|
||||
@@ -130,8 +145,11 @@ class ResetPasswordController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
private function processSendingPasswordResetEmail(string $emailFormData, MailerInterface $mailer, TranslatorInterface $translator): RedirectResponse
|
||||
{
|
||||
private function processSendingPasswordResetEmail(
|
||||
string $emailFormData,
|
||||
MailerInterface $mailer,
|
||||
LoggerInterface $logger
|
||||
): RedirectResponse {
|
||||
$user = $this->entityManager->getRepository(User::class)->findOneBy([
|
||||
'email' => $emailFormData,
|
||||
]);
|
||||
@@ -144,21 +162,17 @@ class ResetPasswordController extends AbstractController
|
||||
try {
|
||||
$resetToken = $this->resetPasswordHelper->generateResetToken($user);
|
||||
} catch (ResetPasswordExceptionInterface $e) {
|
||||
// If you want to tell the user why a reset email was not sent, uncomment
|
||||
// the lines below and change the redirect to 'app_forgot_password_request'.
|
||||
// Caution: This may reveal if a user is registered or not.
|
||||
//
|
||||
// $this->addFlash('reset_password_error', sprintf(
|
||||
// '%s - %s',
|
||||
// $translator->trans(ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE, [], 'ResetPasswordBundle'),
|
||||
// $translator->trans($e->getReason(), [], 'ResetPasswordBundle')
|
||||
// ));
|
||||
$logger->error('> [ResetPasswordController@processSendingPasswordResetEmail] ' . $e->getMessage());
|
||||
|
||||
$this->addFlash(
|
||||
'reset_password_error',
|
||||
'Your password reset token could not be generated. If you\'re the system administrator, check the server logs for more details.'
|
||||
);
|
||||
|
||||
return $this->redirectToRoute('app_check_email');
|
||||
}
|
||||
|
||||
$email = (new TemplatedEmail())
|
||||
->from(new Address('notify@caldwell.digital', 'Torsearch'))
|
||||
->to((string) $user->getEmail())
|
||||
->subject('Your password reset request')
|
||||
->htmlTemplate('user/reset_password/email.html.twig')
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace App\User\Framework\EventListener;
|
||||
|
||||
use App\Base\ConfigResolver;
|
||||
use App\Base\Util\Broadcaster;
|
||||
use App\Base\Service\Broadcaster;
|
||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
|
||||
|
||||
59
src/User/Framework/Form/GettingStartedFilterForm.php
Normal file
59
src/User/Framework/Form/GettingStartedFilterForm.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\User\Framework\Form;
|
||||
|
||||
use Aimeos\Map;
|
||||
use App\User\Database\CountryLanguages;
|
||||
use App\User\Database\ProviderList;
|
||||
use App\User\Database\QualityList;
|
||||
use App\User\Framework\Repository\PreferenceOptionRepository;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class GettingStartedFilterForm extends AbstractType
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PreferenceOptionRepository $preferenceOptionRepository,
|
||||
) {}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$this->addChoiceField($builder, 'language', CountryLanguages::asSelectOptions());
|
||||
$this->addChoiceField($builder, 'quality', QualityList::asSelectOptions());
|
||||
$this->addChoiceField($builder, 'provider', ProviderList::asSelectOptions());
|
||||
$this->addChoiceField($builder, 'resolution', $this->getPreferenceChoices('resolution'));
|
||||
$this->addChoiceField($builder, 'codec', $this->getPreferenceChoices('codec'));
|
||||
}
|
||||
|
||||
private function addChoiceField(FormBuilderInterface $builder, string $fieldName, array $choices): void
|
||||
{
|
||||
$question = [
|
||||
'attr' => ['class' => 'w-full text-input mb-4'],
|
||||
'label_attr' => ['class' => 'w-full block font-semibold'],
|
||||
'choices' => $this->addDefaultChoice($choices),
|
||||
];
|
||||
$builder->add($fieldName, ChoiceType::class, $question);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([]);
|
||||
}
|
||||
|
||||
private function getPreferenceChoices(string $preference): array
|
||||
{
|
||||
$options = $this->preferenceOptionRepository->findBy(['preference' => $preference]);
|
||||
$result = [];
|
||||
foreach ($options as $item) {
|
||||
$result[$item->getName()] = $item->getId();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function addDefaultChoice(array $choices): iterable
|
||||
{
|
||||
return ['n/a' => null] + $choices;
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class RegistrationFormType extends AbstractType
|
||||
'message' => 'Please enter a password',
|
||||
]),
|
||||
new Length([
|
||||
'min' => 6,
|
||||
'min' => 8,
|
||||
'minMessage' => 'Your password should be at least {{ limit }} characters',
|
||||
// max length allowed by Symfony for security reasons
|
||||
'max' => 4096,
|
||||
|
||||
57
src/User/Framework/Security/OidcUserProvider.php
Normal file
57
src/User/Framework/Security/OidcUserProvider.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\User\Framework\Security;
|
||||
|
||||
use App\User\Framework\Entity\User;
|
||||
use App\User\Framework\Repository\UserRepository;
|
||||
use Drenso\OidcBundle\Exception\OidcException;
|
||||
use Drenso\OidcBundle\Model\OidcTokens;
|
||||
use Drenso\OidcBundle\Model\OidcUserData;
|
||||
use Drenso\OidcBundle\Security\UserProvider\OidcUserProviderInterface;
|
||||
use Symfony\Component\PasswordHasher\PasswordHasherInterface;
|
||||
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
|
||||
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
||||
use Symfony\Component\Security\Core\User\OidcUser;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
class OidcUserProvider implements OidcUserProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserRepository $userRepository,
|
||||
) {}
|
||||
|
||||
public function ensureUserExists(string $userIdentifier, OidcUserData $userData, OidcTokens $tokens): void
|
||||
{
|
||||
$user = $this->userRepository->findOneBy(['email' => $userIdentifier]);
|
||||
|
||||
if (null === $user) {
|
||||
$user = new User()
|
||||
->setEmail(!empty($userData->getEmail()) ? $userData->getEmail() : $userData->getSub())
|
||||
->setName(!empty($userData->getFullName()) ? $userData->getFullName() : $userData->getGivenName())
|
||||
->setPassword('n/a')
|
||||
;
|
||||
$this->userRepository->getEntityManager()->persist($user);
|
||||
$this->userRepository->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function loadOidcUser(string $userIdentifier): UserInterface
|
||||
{
|
||||
return $this->userRepository->findOneBy(['email' => $userIdentifier]);
|
||||
}
|
||||
|
||||
public function refreshUser(UserInterface $user): UserInterface
|
||||
{
|
||||
return $this->userRepository->findOneBy(['email' => $user->getUserIdentifier()]);
|
||||
}
|
||||
|
||||
public function supportsClass(string $class): bool
|
||||
{
|
||||
return User::class === $class || OidcUser::class === $class;
|
||||
}
|
||||
|
||||
public function loadUserByIdentifier(string $identifier): UserInterface
|
||||
{
|
||||
return $this->userRepository->findOneBy(['email' => $identifier]);
|
||||
}
|
||||
}
|
||||
15
symfony.lock
15
symfony.lock
@@ -50,6 +50,18 @@
|
||||
"migrations/.gitignore"
|
||||
]
|
||||
},
|
||||
"drenso/symfony-oidc-bundle": {
|
||||
"version": "4.2",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "2.0",
|
||||
"ref": "e2b975158d940a191f48e3ff2c59108a1d7225e6"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/drenso_oidc.yaml"
|
||||
]
|
||||
},
|
||||
"php-http/discovery": {
|
||||
"version": "1.20",
|
||||
"recipe": {
|
||||
@@ -74,6 +86,9 @@
|
||||
"phpstan.dist.neon"
|
||||
]
|
||||
},
|
||||
"spomky-labs/pwa-bundle": {
|
||||
"version": "1.2.5"
|
||||
},
|
||||
"stof/doctrine-extensions-bundle": {
|
||||
"version": "1.14",
|
||||
"recipe": {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
{{ pwa() }}
|
||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
|
||||
{% block stylesheets %}
|
||||
@@ -15,7 +16,7 @@
|
||||
</head>
|
||||
<body class="bg-cyan-950 flex flex-col h-full">
|
||||
<h1 class="px-4 py-4 text-3xl font-extrabold text-orange-500">Torsearch</h1>
|
||||
<div class="flex flex-col justify-center items-center">
|
||||
<div class="p-4 flex flex-col justify-center items-center">
|
||||
{% block body %}{% endblock %}
|
||||
<div class="mt-2 inline-flex gap-4 justify-between text-white">
|
||||
<a class="text-sm" href="{{ path('app_login') }}">Sign In</a>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
{{ pwa() }}
|
||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
|
||||
{% block stylesheets %}
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
mediaType: mediaType,
|
||||
imdbId: imdbId
|
||||
}) }}">
|
||||
<h3 class="text-center text-white text-xl md:text-base md:max-w-[16ch]">{{ title }}</h3>
|
||||
<h3 class="text-center text-white md:text-xl md:text-base md:max-w-[16ch]">{{ title }}</h3>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</twig:Card>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<twig:Card title="Popular Movies" contentClass="flex flex-col gap-4 md:flex-row md:justify-between w-full">
|
||||
<twig:Card title="Popular Movies" contentClass="grid grid-cols-2 gap-4 md:flex md:flex-row md:justify-between w-full">
|
||||
{% for movie in popular_movies %}
|
||||
<twig:Poster imdbId="{{ movie.imdbId }}"
|
||||
tmdbId="{{ movie.tmdbId }}"
|
||||
@@ -32,7 +32,7 @@
|
||||
/>
|
||||
{% endfor %}
|
||||
</twig:Card>
|
||||
<twig:Card title="Popular TV Shows" contentClass="flex flex-col md:flex-row justify-between w-full">
|
||||
<twig:Card title="Popular TV Shows" contentClass="grid grid-cols-2 gap-4 md:flex flex-col md:flex-row justify-between w-full">
|
||||
{% for movie in popular_tvshows %}
|
||||
<twig:Poster imdbId="{{ movie.imdbId }}"
|
||||
tmdbId="{{ movie.tmdbId }}"
|
||||
|
||||
20
templates/user/getting_started/filter.html.twig
Normal file
20
templates/user/getting_started/filter.html.twig
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends 'bare.html.twig' %}
|
||||
|
||||
{% block title %}Getting Started — Torsearch{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex flex-col bg-orange-500/50 p-4 rounded-lg gap-4 w-full md:w-[420px] border-orange-500 border-2 text-gray-50 animate-fade">
|
||||
<h2 class="text-2xl text-bold text-center text-gray-50">Getting Started</h2>
|
||||
<p class="mb-1">Now let's create your first Filter.</p>
|
||||
{# <p class="mb-2 text-sm">Your filter will be pre-applied to your results, so you're only shown what you want to see. Don't worry, though, you can toggle each filter option afterwards, so you can see the rest of the results.</p>#}
|
||||
|
||||
{{ form_start(form) }}
|
||||
{{ form_row(form.language) }}
|
||||
{{ form_row(form.quality) }}
|
||||
{{ form_row(form.provider) }}
|
||||
{{ form_row(form.resolution) }}
|
||||
{{ form_row(form.codec) }}
|
||||
<button class="submit-button">Save</button>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block title %}Getting Started — Torsearch{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex flex-col bg-orange-500/50 p-4 rounded-lg gap-4 min-w-96 border-orange-500 border-2 text-gray-50">
|
||||
<div class="flex flex-col bg-orange-500/50 p-4 rounded-lg gap-4 w-full md:w-[420px] border-orange-500 border-2 text-gray-50 animate-fade">
|
||||
<h2 class="text-2xl text-bold text-center text-gray-50">Getting Started</h2>
|
||||
<p class="mb-2">Let's get started by creating your first User.</p>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block title %}Log in — Torsearch{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex flex-col bg-orange-500/50 p-4 rounded-lg gap-4 min-w-96 border-orange-500 border-2 text-gray-50">
|
||||
<div class="flex flex-col bg-orange-500/50 p-4 rounded-lg gap-4 w-full md:w-[420px] border-orange-500 border-2 text-gray-50 animate-fade">
|
||||
<h2 class="text-xl font-bold">Login</h2>
|
||||
<form method="post" class="flex flex-col gap-2">
|
||||
{% if error %}
|
||||
@@ -52,10 +52,16 @@
|
||||
<button type="submit" class="bg-green-600/40 px-1.5 py-1 w-full rounded-md text-gray-50 backdrop-filter backdrop-blur-sm border-2 border-green-500 hover:bg-green-700/40">
|
||||
Sign in
|
||||
</button>
|
||||
|
||||
<div class="flex">
|
||||
<a href="{{ path('app_forgot_password_request') }}">Forgot password?</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if show_oidc_button == "oidc" %}
|
||||
<a href="{{ path('app_login_oidc') }}" class="bg-sky-950/60 px-1.5 py-1 w-full rounded-md text-gray-50 text-center backdrop-filter backdrop-blur-sm border-2 border-gray-950 hover:bg-orange-700/40">
|
||||
Sign in with OIDC
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<div class="flex">
|
||||
<a href="{{ path('app_forgot_password_request') }}">Forgot password?</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends 'bare.html.twig' %}
|
||||
|
||||
{% block title %}Password Reset Email Sent{% endblock %}
|
||||
{% block title %}Password Reset Email Sent — Torsearch{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex flex-col bg-orange-500/50 p-4 rounded-lg gap-4 max-w-[420px] border-orange-500 border-2 text-gray-50">
|
||||
<div class="flex flex-col bg-orange-500/50 p-4 rounded-lg gap-4 w-full md:w-[420px] border-orange-500 border-2 text-gray-50">
|
||||
<h2 class="text-xl font-bold">Head over to your email</h2>
|
||||
|
||||
<div class="mb-3 flex flex-col gap-4">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block title %}Reset your password — Torsearch{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex flex-col bg-orange-500/50 p-4 rounded-lg gap-4 max-w-[420px] border-orange-500 border-2 text-gray-50">
|
||||
<div class="flex flex-col bg-orange-500/50 p-4 rounded-lg gap-4 w-full md:w-[420px] border-orange-500 border-2 text-gray-50">
|
||||
<h2 class="text-xl font-bold">Reset your password</h2>
|
||||
|
||||
<div class="mb-3">
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<form name="reset_password_request_form" method="post" class="flex flex-col gap-2">
|
||||
{% for flash_error in app.flashes('reset_password_error') %}
|
||||
<div class="mb-3 p-2 bg-rose-500 text-black text-semibold rounded-md" role="alert">{{ flash_error }}</div>
|
||||
<div class="mb-3 p-2 bg-rose-500 text-black font-semibold rounded-md" role="alert">{{ flash_error }}</div>
|
||||
{% endfor %}
|
||||
|
||||
<label for="reset_password_request_form_email" class="required flex flex-col mb-2">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block title %}Reset your password — Torsearch{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="flex flex-col bg-orange-500/50 p-4 rounded-lg gap-4 min-w-96 border-orange-500 border-2 text-gray-50">
|
||||
<div class="flex flex-col bg-orange-500/50 p-4 rounded-lg gap-4 w-full md:w-[420px] border-orange-500 border-2 text-gray-50">
|
||||
<h2 class="text-xl font-bold text-white">Reset your password</h2>
|
||||
|
||||
<div class="mb-2">
|
||||
|
||||
Reference in New Issue
Block a user