add autodeploy and add basic auth for file uploader
Some checks failed
Deploy back2 / deploy (push) Failing after 46s
Some checks failed
Deploy back2 / deploy (push) Failing after 46s
This commit is contained in:
@@ -68,3 +68,7 @@ FILE_LINKS_ENABLED=false
|
||||
FILE_LINKS_DISK=local
|
||||
FILE_LINKS_DIRECTORY=temporary-links
|
||||
FILE_LINKS_MAX_SIZE_KB=10240
|
||||
FILE_LINKS_UPLOAD_USER_1=uploader1
|
||||
FILE_LINKS_UPLOAD_PASSWORD_1=change-me-1
|
||||
FILE_LINKS_UPLOAD_USER_2=uploader2
|
||||
FILE_LINKS_UPLOAD_PASSWORD_2=change-me-2
|
||||
|
||||
68
.gitea/workflows/deploy.yml
Normal file
68
.gitea/workflows/deploy.yml
Normal file
@@ -0,0 +1,68 @@
|
||||
name: Deploy back2
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
||||
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
|
||||
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
|
||||
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup SSH key
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keyscan -p "${DEPLOY_PORT:-22}" -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Deploy Laravel
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
ssh -p "${DEPLOY_PORT:-22}" "$DEPLOY_USER@$DEPLOY_HOST" "DEPLOY_PATH='$DEPLOY_PATH' bash -s" <<'EOF'
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "${DEPLOY_PATH:-}" ]; then
|
||||
echo "DEPLOY_PATH is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$DEPLOY_PATH"
|
||||
|
||||
if [ ! -d .git ]; then
|
||||
echo "Expected a git repository at $DEPLOY_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git fetch origin main
|
||||
git checkout main
|
||||
git reset --hard origin/main
|
||||
|
||||
if [ -f composer.json ]; then
|
||||
composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev
|
||||
fi
|
||||
|
||||
if [ -f artisan ]; then
|
||||
php artisan down --retry=60 || true
|
||||
trap 'php artisan up || true' EXIT
|
||||
|
||||
php artisan optimize:clear
|
||||
php artisan migrate --force
|
||||
php artisan storage:link || true
|
||||
php artisan config:cache
|
||||
php artisan view:cache
|
||||
php artisan queue:restart || true
|
||||
fi
|
||||
EOF
|
||||
75
README.md
75
README.md
@@ -1,59 +1,50 @@
|
||||
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
|
||||
</p>
|
||||
## Repo for site https://back2.iro.kosipov.ru
|
||||
|
||||
## About Laravel
|
||||
### Autodeploy (Gitea Actions)
|
||||
|
||||
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
|
||||
Workflow file: `.gitea/workflows/deploy.yml`
|
||||
|
||||
- [Simple, fast routing engine](https://laravel.com/docs/routing).
|
||||
- [Powerful dependency injection container](https://laravel.com/docs/container).
|
||||
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
|
||||
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
|
||||
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
|
||||
- [Robust background job processing](https://laravel.com/docs/queues).
|
||||
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
|
||||
Deploy starts automatically on every push to `main`.
|
||||
|
||||
Laravel is accessible, powerful, and provides tools required for large, robust applications.
|
||||
Required repository secrets in Gitea:
|
||||
|
||||
## Learning Laravel
|
||||
- `DEPLOY_HOST` - host of the production server (example: `back2.iro.kosipov.ru`)
|
||||
- `DEPLOY_USER` - SSH user for deploy
|
||||
- `DEPLOY_PATH` - absolute path to Laravel project on server
|
||||
- `DEPLOY_SSH_KEY` - private SSH key content (for `DEPLOY_USER`)
|
||||
- `DEPLOY_PORT` - optional, defaults to `22`
|
||||
|
||||
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application.
|
||||
What deployment does:
|
||||
|
||||
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
|
||||
- updates project to `origin/main`
|
||||
- runs `composer install --no-dev --optimize-autoloader`
|
||||
- enables maintenance mode (`php artisan down`)
|
||||
- clears stale caches, runs migrations, recreates Laravel caches
|
||||
- restarts queue workers (`php artisan queue:restart`)
|
||||
- disables maintenance mode (`php artisan up`)
|
||||
|
||||
## Laravel Sponsors
|
||||
Server prerequisites:
|
||||
|
||||
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
|
||||
- `php`, `composer`, `git`, `bash` available in PATH
|
||||
- deploy user has permissions for project files and artisan commands
|
||||
|
||||
### Premium Partners
|
||||
### File Upload Basic Auth
|
||||
|
||||
- **[Vehikl](https://vehikl.com)**
|
||||
- **[Tighten Co.](https://tighten.co)**
|
||||
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
|
||||
- **[64 Robots](https://64robots.com)**
|
||||
- **[Curotec](https://www.curotec.com/services/technologies/laravel)**
|
||||
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
|
||||
- **[Redberry](https://redberry.international/laravel-development)**
|
||||
- **[Active Logic](https://activelogic.com)**
|
||||
Upload endpoints and upload page are protected by HTTP Basic Auth with 2 users from `.env`:
|
||||
|
||||
## Contributing
|
||||
- `FILE_LINKS_UPLOAD_USER_1`
|
||||
- `FILE_LINKS_UPLOAD_PASSWORD_1`
|
||||
- `FILE_LINKS_UPLOAD_USER_2`
|
||||
- `FILE_LINKS_UPLOAD_PASSWORD_2`
|
||||
|
||||
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
|
||||
Protected routes:
|
||||
|
||||
## Code of Conduct
|
||||
- `GET /files`
|
||||
- `POST /files`
|
||||
- `POST /api/files/upload`
|
||||
|
||||
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
|
||||
Download routes stay accessible by signed link:
|
||||
|
||||
## Security Vulnerabilities
|
||||
|
||||
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
|
||||
|
||||
## License
|
||||
|
||||
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
||||
- `GET /files/download/{path}` (signed URL)
|
||||
- `GET /api/files/download/{path}` (signed URL)
|
||||
|
||||
42
app/Http/Middleware/EnsureFileUploadBasicAuth.php
Normal file
42
app/Http/Middleware/EnsureFileUploadBasicAuth.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureFileUploadBasicAuth
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$authorizedUsers = config('file_links.upload_basic_auth_users', []);
|
||||
$username = (string) $request->getUser();
|
||||
$password = (string) $request->getPassword();
|
||||
|
||||
foreach ($authorizedUsers as $authorizedUser) {
|
||||
$validUsername = isset($authorizedUser['username']) && hash_equals((string) $authorizedUser['username'], $username);
|
||||
$validPassword = isset($authorizedUser['password']) && hash_equals((string) $authorizedUser['password'], $password);
|
||||
|
||||
if ($validUsername && $validPassword) {
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->unauthorizedResponse($request);
|
||||
}
|
||||
|
||||
private function unauthorizedResponse(Request $request): Response
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
return new JsonResponse(['message' => 'Unauthorized'], Response::HTTP_UNAUTHORIZED, [
|
||||
'WWW-Authenticate' => 'Basic realm="File Upload"',
|
||||
]);
|
||||
}
|
||||
|
||||
return response('Unauthorized', Response::HTTP_UNAUTHORIZED, [
|
||||
'WWW-Authenticate' => 'Basic realm="File Upload"',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,9 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
//
|
||||
$middleware->alias([
|
||||
'file.upload.basic' => \App\Http\Middleware\EnsureFileUploadBasicAuth::class,
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
//
|
||||
|
||||
@@ -5,5 +5,14 @@ return [
|
||||
'disk' => env('FILE_LINKS_DISK', 'local'),
|
||||
'directory' => env('FILE_LINKS_DIRECTORY', 'temporary-links'),
|
||||
'max_size_kb' => (int) env('FILE_LINKS_MAX_SIZE_KB', 10240),
|
||||
'upload_basic_auth_users' => array_values(array_filter([
|
||||
[
|
||||
'username' => env('FILE_LINKS_UPLOAD_USER_1'),
|
||||
'password' => env('FILE_LINKS_UPLOAD_PASSWORD_1'),
|
||||
],
|
||||
[
|
||||
'username' => env('FILE_LINKS_UPLOAD_USER_2'),
|
||||
'password' => env('FILE_LINKS_UPLOAD_PASSWORD_2'),
|
||||
],
|
||||
], static fn (array $user): bool => filled($user['username']) && filled($user['password']))),
|
||||
];
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ Route::prefix('habits')->group(function () {
|
||||
});
|
||||
|
||||
Route::prefix('files')->group(function () {
|
||||
Route::post('/upload', [FileLinkController::class, 'upload']);
|
||||
Route::post('/upload', [FileLinkController::class, 'upload'])->middleware('file.upload.basic');
|
||||
Route::get('/download/{path}', [FileLinkController::class, 'download'])
|
||||
->where('path', '.*')
|
||||
->middleware('signed')
|
||||
|
||||
@@ -8,8 +8,10 @@ Route::get('/', function () {
|
||||
return view('welcome');
|
||||
});
|
||||
|
||||
Route::get('/files', [FileLinkController::class, 'index'])->name('files.index');
|
||||
Route::post('/files', [FileLinkController::class, 'uploadWeb'])->name('files.upload');
|
||||
Route::middleware('file.upload.basic')->group(function () {
|
||||
Route::get('/files', [FileLinkController::class, 'index'])->name('files.index');
|
||||
Route::post('/files', [FileLinkController::class, 'uploadWeb'])->name('files.upload');
|
||||
});
|
||||
Route::get('/files/download/{path}', [FileLinkController::class, 'download'])
|
||||
->where('path', '.*')
|
||||
->middleware('signed')
|
||||
|
||||
@@ -9,14 +9,60 @@ use Tests\TestCase;
|
||||
|
||||
class FileLinkControllerTest extends TestCase
|
||||
{
|
||||
public function test_upload_is_unavailable_when_feature_is_disabled(): void
|
||||
protected function setUp(): void
|
||||
{
|
||||
Config::set('file_links.enabled', false);
|
||||
parent::setUp();
|
||||
|
||||
Config::set('file_links.upload_basic_auth_users', [
|
||||
['username' => 'uploader1', 'password' => 'password1'],
|
||||
['username' => 'uploader2', 'password' => 'password2'],
|
||||
]);
|
||||
}
|
||||
|
||||
private function authHeader(string $username = 'uploader1', string $password = 'password1'): array
|
||||
{
|
||||
return [
|
||||
'Authorization' => 'Basic ' . base64_encode($username . ':' . $password),
|
||||
];
|
||||
}
|
||||
|
||||
public function test_upload_requires_basic_auth(): void
|
||||
{
|
||||
Config::set('file_links.enabled', true);
|
||||
|
||||
$response = $this->postJson('/api/files/upload', [
|
||||
'file' => UploadedFile::fake()->create('sample.txt', 1),
|
||||
]);
|
||||
|
||||
$response->assertUnauthorized();
|
||||
$response->assertHeader('WWW-Authenticate', 'Basic realm="File Upload"');
|
||||
}
|
||||
|
||||
public function test_upload_accepts_second_basic_auth_user(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
Config::set('file_links.enabled', true);
|
||||
Config::set('file_links.disk', 'local');
|
||||
Config::set('file_links.directory', 'temporary-links');
|
||||
|
||||
$response = $this->withHeaders($this->authHeader('uploader2', 'password2'))->postJson(
|
||||
'/api/files/upload',
|
||||
['file' => UploadedFile::fake()->create('sample.txt', 1)]
|
||||
);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonStructure(['path', 'expiresAt', 'url']);
|
||||
}
|
||||
|
||||
public function test_upload_is_unavailable_when_feature_is_disabled(): void
|
||||
{
|
||||
Config::set('file_links.enabled', false);
|
||||
|
||||
$response = $this->withHeaders($this->authHeader())->postJson(
|
||||
'/api/files/upload',
|
||||
['file' => UploadedFile::fake()->create('sample.txt', 1)]
|
||||
);
|
||||
|
||||
$response->assertNotFound();
|
||||
}
|
||||
|
||||
@@ -27,9 +73,10 @@ class FileLinkControllerTest extends TestCase
|
||||
Config::set('file_links.disk', 'local');
|
||||
Config::set('file_links.directory', 'temporary-links');
|
||||
|
||||
$response = $this->postJson('/api/files/upload', [
|
||||
'file' => UploadedFile::fake()->create('sample.txt', 1),
|
||||
]);
|
||||
$response = $this->withHeaders($this->authHeader())->postJson(
|
||||
'/api/files/upload',
|
||||
['file' => UploadedFile::fake()->create('sample.txt', 1)]
|
||||
);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonStructure(['path', 'expiresAt', 'url']);
|
||||
@@ -50,4 +97,3 @@ class FileLinkControllerTest extends TestCase
|
||||
$downloadResponse->assertHeader('content-disposition');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,11 +9,36 @@ use Tests\TestCase;
|
||||
|
||||
class FileLinkWebTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Config::set('file_links.upload_basic_auth_users', [
|
||||
['username' => 'uploader1', 'password' => 'password1'],
|
||||
['username' => 'uploader2', 'password' => 'password2'],
|
||||
]);
|
||||
}
|
||||
|
||||
private function authHeader(string $username = 'uploader1', string $password = 'password1'): array
|
||||
{
|
||||
return [
|
||||
'Authorization' => 'Basic ' . base64_encode($username . ':' . $password),
|
||||
];
|
||||
}
|
||||
|
||||
public function test_files_page_requires_basic_auth(): void
|
||||
{
|
||||
$response = $this->get('/files');
|
||||
|
||||
$response->assertUnauthorized();
|
||||
$response->assertHeader('WWW-Authenticate', 'Basic realm="File Upload"');
|
||||
}
|
||||
|
||||
public function test_files_page_shows_disabled_state(): void
|
||||
{
|
||||
Config::set('file_links.enabled', false);
|
||||
|
||||
$response = $this->get('/files');
|
||||
$response = $this->withHeaders($this->authHeader())->get('/files');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertSee('Функция отключена конфигом');
|
||||
@@ -26,7 +51,7 @@ class FileLinkWebTest extends TestCase
|
||||
Config::set('file_links.disk', 'local');
|
||||
Config::set('file_links.directory', 'temporary-links');
|
||||
|
||||
$response = $this->post('/files', [
|
||||
$response = $this->withHeaders($this->authHeader())->post('/files', [
|
||||
'file' => UploadedFile::fake()->create('web-sample.txt', 1),
|
||||
]);
|
||||
|
||||
@@ -35,4 +60,3 @@ class FileLinkWebTest extends TestCase
|
||||
$response->assertSessionHas('file_expires_at');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user