5 Commits
Author SHA1 Message Date
andrew 3c1253ee45 Merge branch 'develop'
Push Docker / docker (push) Successful in 4m47s
Push Develop / docker (push) Successful in 4m44s
Sync GitHub / sync (push) Successful in 6s
2026-06-17 13:27:48 -04:00
andrew 70bf811b1d gitea action cleanup
Push Develop / docker (push) Successful in 6m49s
Sync GitHub / sync (push) Successful in 7s
2026-06-17 10:55:26 -04:00
andrew 3269151e9b new workflow to build and push to docker hub
Sync GitHub / sync (push) Failing after 6s
2026-06-14 18:27:32 -04:00
andrew 1da2188bdf Gitea action to sync to GitHub
Sync GitHub / sync (push) Successful in 7s
2026-06-13 21:28:59 -04:00
andrew 7cf7e04d17 extracted interfaces for services, to aid in creating tests 2026-06-05 15:15:21 -04:00
13 changed files with 186 additions and 20 deletions
+34
View File
@@ -0,0 +1,34 @@
name: Push Develop
on:
push:
branches:
- 'main'
- 'develop'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build image
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: |
${{ vars.DOCKERHUB_TARGET }}:develop
+34
View File
@@ -0,0 +1,34 @@
name: Push Docker
on:
push:
tags:
- '*.*.*'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build image
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: |
${{ vars.DOCKERHUB_TARGET }}:latest
${{ vars.DOCKERHUB_TARGET }}:${{ github.ref_name }}
+37
View File
@@ -0,0 +1,37 @@
name: Sync GitHub
on:
push:
branches:
- '**'
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git
run: |
git config --global user.name "Andrew Sync"
git config --global user.email "sync@digitaladapt.com"
- name: Add GitHub Remote
env:
SYNC_TOKEN: ${{ secrets.SYNC_GITHUB_TOKEN }}
SYNC_TARGET: ${{ vars.SYNC_GITHUB_TARGET }}
run: |
git remote add github "https://digitaladapt:${SYNC_TOKEN}@github.com/$SYNC_TARGET"
- name: Push Current Branch
run: |
git push github HEAD:${GITHUB_REF_NAME}
- name: Push Tags
run: |
git push github --tags
+3 -3
View File
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Command;
use App\PersistCache;
use App\Service\BackupCodeManager;
use App\Service\BackupCodeInterface;
use Psr\Cache\InvalidArgumentException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@@ -15,8 +15,8 @@ use Symfony\Component\Console\Output\OutputInterface;
* usage: php bin/console app:generate-backup-codes [count] */
final class GenerateBackupCodesCommand extends Command {
public function __construct(
private readonly BackupCodeManager $manager,
private readonly PersistCache $persistCache,
private readonly BackupCodeInterface $manager,
private readonly PersistCache $persistCache,
) {
parent::__construct();
}
+2 -2
View File
@@ -3,7 +3,7 @@ declare(strict_types=1);
namespace App\Listener;
use App\Service\DomainManager;
use App\Service\DomainInterface;
use App\Trait\CookieNameTrait;
use App\Trait\HasLoggerTrait;
use App\Trait\StringTrait;
@@ -20,7 +20,7 @@ final readonly class AcceptListener {
public function __construct(
private CacheItemPoolInterface $sessionCache,
private DomainManager $domainManager,
private DomainInterface $domainManager,
) {}
/** @throws InvalidArgumentException */
+4 -4
View File
@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Listener;
use App\ConfigBag;
use App\Service\DomainManager;
use App\Service\DomainInterface;
use App\Trait\CookieNameTrait;
use App\Trait\HasLoggerTrait;
use App\Trait\MakeNonceTrait;
@@ -24,9 +24,9 @@ final readonly class InterceptListener {
use MakeNonceTrait;
public function __construct(
private ConfigBag $config,
private DomainManager $domainManager,
private Environment $twig,
private ConfigBag $config,
private DomainInterface $domainManager,
private Environment $twig,
) {}
/** @throws InvalidArgumentException|RuntimeError|SyntaxError|LoaderError */
+4 -4
View File
@@ -5,8 +5,8 @@ namespace App\Listener;
use App\ConfigBag;
use App\Data\Payload;
use App\Service\DomainManager;
use App\Service\LoginManager;
use App\Service\DomainInterface;
use App\Service\LoginInterface;
use App\Trait\CookieNameTrait;
use App\Trait\HasLoggerTrait;
use App\Trait\MakeNonceTrait;
@@ -34,8 +34,8 @@ final readonly class LoginListener {
public function __construct(
private Environment $twig,
#[Target('login_limiter')] RateLimiterFactoryInterface $rateLimiter,
private DomainManager $domainManager,
private LoginManager $loginManager,
private DomainInterface $domainManager,
private LoginInterface $loginManager,
private ConfigBag $config,
) {
$this->rateLimiter = $rateLimiter;
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace App\Service;
use Exception;
use Psr\Cache\InvalidArgumentException;
/** backup-codes are caseinsensitive alphanumeric strings
* they are single-use and marked as used after successful authentication */
interface BackupCodeInterface {
/** generate a set of backup-codes and return them
* @param int $count Number of codes to generate
* @return string[] Generated backup codes
* @throws InvalidArgumentException|Exception */
public function generate(int $count = 0): array;
/** @throws InvalidArgumentException */
public function expire(): void;
/** check if backup-code is valid and mark it as used
* @param string $code Code supplied by the client
* @return bool true if the code is valid and unused
* @throws InvalidArgumentException */
public function verifyAndConsume(string $code): bool;
}
+2 -3
View File
@@ -13,9 +13,8 @@ use Psr\Cache\InvalidArgumentException;
use App\Trait\GetTotpTrait;
/** backup-codes are caseinsensitive alphanumeric strings
* they are single-use and marked as used after successful authentication
*/
final readonly class BackupCodeManager {
* they are single-use and marked as used after successful authentication */
final readonly class BackupCodeManager implements BackupCodeInterface {
use GetTotpTrait;
use HasLoggerTrait;
use StringTrait;
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace App\Service;
interface DomainInterface {
/** IE: "auth.example.com" or null if not using a separate subdomain
* @return ?string Returns auth subdomain if configured, otherwise null */
public function getAuthSubdomain(): ?string;
/** check if given url is an acceptable url for redirection
* @param string $url Where we are thinking of sending the user
* @return bool Returns true if it is acceptable to send the user there */
public function validReturn(string $url): bool;
/** check if host-base matches auth-base
* @param string $host
* @return bool returns true if and only if host matches base domain of auth */
public function matchesAuth(string $host): bool;
/** IE: "example.com" if central auth is something like "auth.example.com"
* @return string|null returns base domain if we are doing central auth */
public function authBase(): ?string;
}
+1 -1
View File
@@ -5,7 +5,7 @@ namespace App\Service;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
final readonly class DomainManager {
final readonly class DomainManager implements DomainInterface {
/* top-level-domains which are known to have multiple parts */
private const array TLD = [
'ai' => ['com','net','off','org'],
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace App\Service;
use App\Data\Payload;
use Psr\Cache\InvalidArgumentException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
interface LoginInterface {
/** @throws InvalidArgumentException */
public function checkToken(Payload $payload, Request $request): ?Response;
}
+3 -3
View File
@@ -18,7 +18,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\Uid\Ulid;
final readonly class LoginManager {
final readonly class LoginManager implements LoginInterface {
use CookieNameTrait;
use GetTotpTrait;
use MakeNonceTrait;
@@ -29,8 +29,8 @@ final readonly class LoginManager {
/** @throws InvalidArgumentException */
public function __construct(
CacheItemPoolInterface $sessionCache,
private BackupCodeManager $backupCodeManager,
private DomainManager $domainManager,
private BackupCodeInterface $backupCodeManager,
private DomainInterface $domainManager,
) {
$this->sessionCache = new MonitorCacheKeys($sessionCache);
}